JsonSerializer
The JsonSerializer is the core utility for serializing and deserializing TypeScript objects to and from JSON. It works seamlessly with the @field decorator system and @derivedType decorators to provide type-safe, round-trip serialization that preserves runtime types and method access.
Overview
The JsonSerializer bridges the gap between JSON data and strongly-typed TypeScript class instances, enabling:
- True Object Deserialization: JSON data becomes actual class instances, not plain objects
- Method Preservation: All class methods remain accessible on deserialized instances
- Type Safety: Automatic type resolution and validation during serialization/deserialization
- Polymorphic Support: Automatic handling of derived types and interface implementations
- Seamless Backend Integration: Compatible with .NET backend serialization format
Key Benefits
1. Type-Safe Runtime Objects
Instead of plain JavaScript objects from JSON.parse(), the JsonSerializer creates actual class instances:
import { JsonSerializer } from '@cratis/fundamentals';
// Traditional approach - loses type information
const json = `{"name":"John","age":30}`;
const user = JSON.parse(json); // Plain object, not a User
// With JsonSerializer - maintains type information
const user = JsonSerializer.deserialize(User, json); // Real User instance
console.log(user instanceof User); // ✅ true
2. Business Logic Access
Methods defined on your classes are immediately available on deserialized objects:
export class User {
@field(String)
name!: string;
@field(Number)
age!: number;
getDisplayName(): string {
return `${this.name} (${this.age})`;
}
}
const json = `{"name":"John","age":30}`;
const user = JsonSerializer.deserialize(User, json);
// Method available immediately
console.log(user.getDisplayName()); // ✅ "John (30)"
3. Automatic Polymorphic Resolution
When working with interfaces or base classes, the JsonSerializer automatically deserializes to the correct derived type:
// Different payment types deserialized to their correct classes
@derivedType('credit-card-id')
export class CreditCard implements IPaymentMethod { }
@derivedType('paypal-id')
export class PayPal implements IPaymentMethod { }
const json = `{"method":{"amount":99.99,"_derivedTypeId":"credit-card-id"}}`;
const payment = JsonSerializer.deserialize(Payment, json);
console.log(payment.method instanceof CreditCard); // ✅ true
4. Proper Type Conversion
Complex types like Date and Guid are properly converted during deserialization:
export class Order {
@field(String)
orderId!: string;
@field(Date)
createdAt!: Date;
@field(Guid)
customerId!: Guid;
}
const json = `{"orderId":"123","createdAt":"2023-01-15T10:30:00Z","customerId":"550e8400-e29b-41d4-a716-446655440000"}`;
const order = JsonSerializer.deserialize(Order, json);
console.log(order.createdAt instanceof Date); // ✅ true
console.log(order.customerId instanceof Guid); // ✅ true
Usage
Basic Serialization
Convert class instances to JSON:
const user = new User();
user.name = "John";
user.age = 30;
const json = JsonSerializer.serialize(user);
console.log(json); // {"name":"John","age":30}
Basic Deserialization
Convert JSON strings back to typed instances:
const json = `{"name":"John","age":30}`;
const user = JsonSerializer.deserialize(User, json);
console.log(user instanceof User); // ✅ true
console.log(user.name); // "John"
Array Deserialization
Handle arrays of typed objects:
const json = `[
{"name":"John","age":30},
{"name":"Jane","age":25}
]`;
const users = JsonSerializer.deserializeArray(User, json);
// Array of User instances, not plain objects
Integration with Field Decorators
The JsonSerializer relies on the @field decorator to understand your class structure. For comprehensive details about decorators, field configuration, and advanced patterns, see:
- Field Decorator Documentation - Complete guide to the
@fielddecorator system, runtime type safety, and advanced serialization patterns
Integration with Derived Types
For polymorphic serialization and working with multiple implementations of the same interface, see:
- DerivedTypes Serialization Documentation - Full guide to the
@derivedTypedecorator, polymorphic deserialization, and multi-type scenarios
Best Practices
1. Complete Field Declaration
Ensure all serializable properties are decorated:
// ✅ Good - all fields decorated
export class Product {
@field(String)
name!: string;
@field(Number)
price!: number;
@field(Date)
createdAt!: Date;
}
2. Test Round-Trip Serialization
Verify your objects serialize and deserialize correctly:
const original = new User();
original.name = "John";
original.age = 30;
const json = JsonSerializer.serialize(original);
const deserialized = JsonSerializer.deserialize(User, json);
expect(deserialized.name).toBe(original.name);
expect(deserialized.age).toBe(original.age);
expect(deserialized.getDisplayName()).toBe(original.getDisplayName());
3. Handle Complex Nested Objects
The JsonSerializer automatically handles nested typed objects:
export class Order {
@field(String)
orderId!: string;
@field(Customer) // Nested typed object
customer!: Customer;
@field(Product, true) // Array of typed objects
items!: Product[];
}
const order = JsonSerializer.deserialize(Order, json);
// customer is a Customer instance, items are Product instances
4. Error Handling
Wrap serialization calls in try-catch blocks for production code:
try {
const user = JsonSerializer.deserialize(User, jsonData);
// Process user
} catch (error) {
console.error('Deserialization failed:', error);
}
Common Scenarios
Backend Integration
Deserialize data received from your .NET backend:
// Response from backend
const response = await fetch('/api/users/123');
const json = await response.json();
// Convert to typed instance
const user = JsonSerializer.deserialize(User, JSON.stringify(json));
Working with Collections
const usersJson = await (await fetch('/api/users')).json();
const users = JsonSerializer.deserializeArray(User, JSON.stringify(usersJson));
Polymorphic API Responses
// Single polymorphic field
const paymentJson = `{"method":{"amount":99.99,"_derivedTypeId":"..."}}`;
const payment = JsonSerializer.deserialize(Payment, paymentJson);
// Array of polymorphic types
const methodsJson = `[
{"amount":50,"_derivedTypeId":"credit-card"},
{"amount":40,"_derivedTypeId":"paypal"}
]`;
const methods = JsonSerializer.deserializeArray(IPaymentMethod, methodsJson);
See Also
- Field Decorator - Decorator system for field serialization configuration
- Derived Types - Polymorphic type handling and interface implementations
- Serialization Overview - Complete serialization system documentation