A typescript library to deserialize json into typescript classes and serialize classes into json.
Summary
Installation
npm install typescript-json-serializer --save
# or
yarn add typescript-json-serializerYou also need to set experimentalDecorators and emitDecoratorMetadata to true into the tsconfig.json file.
For example:
{
"compilerOptions": {
...
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
...
}
}Usage
import { JsonSerializer, throwError } from 'typescript-json-serializer'; import { json } from '../json/data'; import { Organization } from '../models/organization'; // Instantiate a default serializer const defaultSerializer = new JsonSerializer(); // Or you can instantiate a serializer with your custom options const customSerializer = new JsonSerializer({ // Throw errors instead of logging errorCallback: throwError, // Allow all nullish values nullishPolicy: { undefined: 'allow', null: 'allow' }, // Disallow additional properties (non JsonProperty) additionalPropertiesPolicy: 'disallow', // e.g. if all the properties in the json object are prefixed by '_' formatPropertyName: (propertyName: string) => `_${propertyName}`, }) // Deserialize const organization = defaultSerializer.deserialize(json, Organization); // Serialize const data = defaultSerializer.serialize(organization);
Examples
Classes
// Import decorators from library import { JsonObject, JsonProperty } from 'typescript-json-serializer'; // Enums export enum Gender { Female, Male, Other } export enum Status { Alive = 'Alive', Sick = 'Sick', DeadAndAlive = 'Dead and alive', Dead = 'Dead' } // Create a JsonObject class: LivingBeing // JsonObject decorator @JsonObject() export class LivingBeing { /** The living being id (PK) */ @JsonProperty() id: number; } // Create a JsonObject class that extends LivingBeing: Human @JsonObject() export class Human extends LivingBeing { constructor( // This comment works // Override LivingBeing id property name // and set required to true @JsonProperty({name: 'humanId', required: true}) public id: number, @JsonProperty() public name: string, @JsonProperty() public gender: Gender, /** This comment works */ @JsonProperty() public readonly birthDate: Date ) { super(); this.id = id; } } // Create a JsonObject class: PhoneNumber @JsonObject() export class PhoneNumber { @JsonProperty() countryCode: string; @JsonProperty() value: string; } // Create a JsonObject class that extends Human: Employee @JsonObject() export class Employee extends Human { /** The employee's email */ @JsonProperty({required: true}) email: string; /** Predicate function to determine if the property type * is PhoneNumber or a primitive type */ @JsonProperty({ type: property => { if (property && property.value !== undefined) { return PhoneNumber; } } }) phoneNumber: PhoneNumber | string; constructor( public name: string, // Override human id property name // (keep the require to true from Human id) @JsonProperty('employeeId') public id: number, public gender: Gender, public birthDate: Date ) { super(name, id, gender, birthDate); } } // Create a JsonObject class that extends LivingBeing: Animal @JsonObject() export class Animal extends LivingBeing { @JsonProperty() name: string | undefined; @JsonProperty() birthDate: Date; @JsonProperty() numberOfPaws: number; @JsonProperty() gender: Gender; // Enum value (string) @JsonProperty() status: Status; // Specify the property name of json property if needed @JsonProperty('childrenIdentifiers') childrenIds: Array<number>; constructor(name: string | undefined) { super(); this.name = name; } } // Create a JsonObject class that extends Animal (which extends LivingBeing): Panther @JsonObject() export class Panther extends Animal { @JsonProperty() color: string; // JsonProperty directly inside the constructor // for property parameters public constructor( name: string | undefined, @JsonProperty() public isSpeckled: boolean ) { super(name); } } // Create a JsonObject class that extends Animal (which extends LivingBeing): Snake // When the library instatiates a class during deserilization it does not know the constructor params value // so you need to specify them. // To ways are available // - by passing default value to those params in the constructor // - by passing the `constructorParams` options to `JsonObject` (see below); it allows to keep clean constructor @JsonObject({ constructorParams: [{}] }) export class Snake extends Animal { @JsonProperty() isPoisonous: boolean; public constructor(args: { name: string | undefined; isPoisonous: boolean }) { super(args.name); this.isPoisonous = args.isPoisonous; } } // Create a JsonObject empty class that extends Animal (which extends LivingBeing): UnknownAnimal @JsonObject() export class UnknownAnimal extends Animal { public constructor(name: string) { super(name); } } // Create a JsonObject class: Zoo // Function to transform coordinates into an array const coordinatesToArray = (coordinates: { x: number; y: number; z: number; }): Array<number> => { return Object.values(coordinates); }; // Function to transform an array into coordinates const arrayToCoordinates = (array: Array<number>): { x: number; y: number; z: number } => { return { x: array[0], y: array[1], z: array[2] }; }; // A predicate function use to determine what is the // right type of the data (Snake or Panther) const snakeOrPanther = animal => { return animal && animal['isPoisonous'] !== undefined ? Snake : Panther; }; @JsonObject() export class Zoo { // Here you do not need to specify the type // inside the decorator @JsonProperty() boss: Employee; @JsonProperty() city: string; @JsonProperty() country: string; // Property with transform functions executed respectively // on serialize and on deserialize @JsonProperty({ beforeDeserialize: arrayToCoordinates, afterSerialize: coordinatesToArray }) coordinates: { x: number; y: number; z: number }; // Possibly undefined Set of none-basic type elements @JsonProperty({ type: Employee, dataStructure: 'set' }) employees: Set<Employee> | undefined; @JsonProperty() id: number; @JsonProperty() name: string; // Array of none-basic type elements where you need to // specify the name of the json property // and use the predicate function to cast the deserialized // object into the correct child class @JsonProperty({ name: 'Animals', type: snakeOrPanther }) animals: Array<Animal>; // Property that can be Panther or Snake type // Use again the predicate function @JsonProperty({ type: snakeOrPanther }) mascot: Panther | Snake; // Dictionary of empty child classes @JsonProperty({ dataStructure: 'dictionary', type: UnknownAnimal }) unknownAnimals: { [id: string]: UnknownAnimal }; // Map of array of PhoneNumber or string @JsonProperty({ type: property => { if (property?.value !== undefined) { return PhoneNumber; } } }) phoneBook: Map<string, Array<PhoneNumber | string>>; // Property which will be not serialized and deserialized // but event accessible and editable from Zoo class. public isFree: boolean = true; public constructor() { } } // Create a JsonObject generic class: Value @JsonObject() export class Value<R> { @JsonProperty() value: R; constructor(value: R) { this.value = value; } } // Create a JsonObject class: Item @JsonObject() export class Item { @JsonProperty({ name: 'name' }) private readonly _name: string; @JsonProperty({ name: 'version' }) private readonly _version: number; constructor(name: string, version: number) { this._name = name; this._version = version; } } // Create a JsonObject class that extends Society: Organization @JsonObject() export class Organization extends Society { @JsonProperty({ type: Zoo }) zoos: Array<Zoo>; @JsonProperty({ dataStructure: 'dictionary' }) zoosName: { [id: string]: string }; // To merge multiple properties in a single one // use the property `names`. // If you don't create your own merge with the `beforeDeserialize` // and `afterSerialize` function, it will just merge properties // in this one when using `deserialize` and split back // when using `serialize` @JsonProperty({ name: [ 'mainShareholder', 'secondaryShareholder', 'thirdShareholder' ], type: Human, beforeDeserialize: value => Object.values(value), afterSerialize: value => { return { mainShareholder: value[0], secondaryShareholder: value[1], thirdShareholder: value[2] }; } }) shareholders: Array<Human>; miscellaneous: Value<Item>; } // Create a JsonObject class: Society @JsonObject() export class Society { // Here note the `required` option with the default value `'4'` // it will not throw because the default value will be set instead of `undefined` @JsonProperty({ required: true }) id: string = '4'; @JsonProperty() name: string; }
Json data
// data.ts export const data: any = { name: 'Zoos Organization', zoosName: { '15': 'The Greatest Zoo', '16': 'Zoo Zoo' }, zoos: [ { id: 15, name: 'The Greatest Zoo', city: 'Bordeaux', coordinates: [1, 2, 3], country: 'France', boss: { employeeId: 1, name: 'Bob Razowsky', birthDate: '1984-04-03T22:00:00.000Z', email: 'bob.razowsky@tgzoo.fr', gender: 1, phoneNumber: '111-111-1111' }, employees: [ { employeeId: 1, name: 'Bob Razowsky', birthDate: '1984-04-03T22:00:00.000Z', email: 'bob.razowsky@tgzoo.fr', gender: 1, phoneNumber: '111-111-1111' }, { employeeId: 2, name: 'Mikasa Ackerman', birthDate: '1984-01-11T22:00:00.000Z', email: 'mikasa.ackerman@tgzoo.fr', gender: 0, phoneNumber: '222-222-2222' }, { employeeId: 3, name: 'Red Redington', birthDate: '1970-12-04T22:00:00.000Z', email: 'red.redington@tgzoo.fr', gender: 1, phoneNumber: '333-333-3333' }, { employeeId: 4, name: 'Fried Richter', birthDate: '1994-04-01T22:00:00.000Z', email: 'fried.richter@tgzoo.fr', gender: 1 } ], Animals: [ { id: 1, name: 'Bagheera', birthDate: '2010-01-11T22:00:00.000Z', numberOfPaws: 4, gender: 1, childrenIdentifiers: [2, 3], color: 'black', isSpeckled: false, status: 'Sick' }, { id: 2, birthDate: '2017-03-10T22:00:00.000Z', numberOfPaws: 4, gender: 0, color: 'blond', isSpeckled: true, status: 'Alive' }, { id: 3, name: 'Ka', birthDate: '2018-09-09T00:00:00.000Z', numberOfPaws: 0, gender: 1, isPoisonous: true }, { id: 4, name: 'Schrodinger', numberOfPaws: 4, gender: 1, color: 'brown', isSpeckled: false, status: 'Dead and alive' } ], mascot: { id: 1, name: 'Bagheera', birthDate: '2010-01-11T22:00:00.000Z', numberOfPaws: 4, gender: 1, childrenIdentifiers: [2, 3], color: 'black', isSpeckled: false, status: 'Sick' }, unknownAnimals: { '1': { name: null } }, phoneBook: { '1': [ { value: '111-111-1111' }, { value: '444-444-4444' } ], '2': [{ value: '222-222-2222' }], '3': ['333-333-3333'] } }, { id: 16, name: 'Zoo Zoo', city: 'Paris', coordinates: [4, 2, 3], country: 'France', boss: { employeeId: 2, name: 'Sully', birthDate: '1984-08-03T22:00:00.000Z', email: 'sully.razowsky@tgzoo.fr', gender: 1, phoneNumber: { countryCode: '33', value: '0111111111' } }, employees: [], Animals: [], mascot: null, unknownAnimals: {} } ], mainShareholder: { humanId: 100, name: 'Elon Musk', birthDate: '1971-06-28T22:00:00.000Z', gender: 1 }, secondaryShareholder: null, miscellaneous: { value: { name: 'Item A', version: 1 } } };
API
Decorators
@JsonObject()
Used to make a class serializable.
Parameters
options
Type: JsonObjectOptions
Optional: true
Description: The option to customize the serialization/deserialization of the target class.
Example
@JsonObject(options) class MyClass {}
@JsonProperty()
Used to make a class property serializable, property will be ignored if not set.
Parameters
options
Type: string | JsonPropertyOptions
Optional: true
Description: The option to customize the serialization/deserialization of the target property.
Example
@JsonProperty(options) myProperty: string;
JsonSerializer
Constructor
constructor(options?: Partial<JsonSerializerOptions>) {}
Parameters
options
Type: Partial<JsonSerializerOptions>
Optional: true
Description: The options to customize the serializer.
Properties
options
Type: Partial<JsonSerializerOptions>
Optional: false
Description: The options to customize the serializer.
Default value:
{ errorCallback: logError, nullishPolicy: { undefined: 'remove', null: 'allow' }, additionalPropertiesPolicy: 'remove' }
Methods
deserialize()
To use when you don't know if the value to deserialize is an object or an array.