Package Exports
- typescript-json-serializer
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (typescript-json-serializer) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
typescript-json-serializer
A typescript library to deserialize json into typescript classes and serialize classes into json.
Installation
npm install typescript-json-serializer --save
# or
yarn add typescript-json-serializer
You also need to set experimentalDecorators and emitDecoratorMetadata to true into the tsconfig.json file.
For example:
{
"compilerOptions": {
...
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
...
}
}
WARNING: If you use CRA (create-react-app) please refer to the Using with Create React App section.
Import
There are two decorators and two functions that you can import inside a typescript file.
import {
JsonProperty,
Serializable,
deserialize,
serialize
} from 'typescript-json-serializer';
Library
Decorators
// Serializable decorator set a class as serializable.
// It can take options as parameter:
// - formatPropertyNames function to format the property names
// provided by the json you want to serialize to match
// with your class property names
type FormatPropertyNameProto = (propertyName: string) => string;
type SerializableOptions = {
formatPropertyNames: FormatPropertyNameProto
}
@Serializable(options?: SerializableOptions)
// JsonProperty decorator set metadata to the property.
// It can take some optional parameters like:
// - the name of json property if diverge from the class property
// (this value override the `formatPropertyNames` option from
// `Serializable` decorator)
// - the type of the property (if needed)
// - a predicate function that return a type (if needed)
// - a function to transform data before serialize
// - a function to transform data after serialize
// - a function to transform data before deserialize
// - a function to transform data after deserialize
// - the names of properties to merge (the `formatPropertyNames`
// from `Serializable` decorator is ignored)
// - a boolean to tell that the property is a dictionary
// - a boolean to tell that the property is required
// (throw an error if undefined, null or missing)
// BREAKING CHANGES: since version 3.0.0
// - onSerialize has become afterSerialize
// - onDeserialize has become beforeDeserialize
// - postDeserialize has become afterDeserialize
type IOProto = (property: any, currentInstance?: any) => {};
type PredicateProto = (property: any, parentProperty?: any) => {};
@JsonProperty(args?:
| string
| {
name?: string,
type?: Function,
beforeSerialize?: IOProto,
afterSerialize?: IOProto,
beforeDeserialize?: IOProto,
afterDeserialize?: IOProto,
isDictionary?: boolean,
required?: boolean
}
| {
name?: string,
predicate?: PredicateProto,
beforeSerialize?: IOProto,
afterSerialize?: IOProto,
beforeDeserialize?: IOProto,
afterDeserialize?: IOProto,
isDictionary?: boolean,
required?: boolean
}
| {
names?: Array<string>,
type?: Function,
beforeSerialize?: IOProto,
afterSerialize?: IOProto,
beforeDeserialize?: IOProto,
afterDeserialize?: IOProto,
required?: boolean
}
| {
names?: Array<string>,
predicate?: PredicateProto,
beforeSerialize?: IOProto,
afterSerialize?: IOProto,
beforeDeserialize?: IOProto,
afterDeserialize?: IOProto,
required?: boolean
})
Functions
// serialize function transform typescript class into json.
// It takes two parameters:
// - a instance of the class to serialize
// - a boolean to remove undefined property (default true)
serialize(instance: any, removeUndefined: boolean = true): any
// deserialize function transform json into typescript class.
// It takes two parameters:
// - json data
// - the class you want to deserialize into
deserialize<T>(json: any, type: new (...params) => T): T
Example
Classes
// Import decorators from library
import { Serializable, 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 serializable class: LivingBeing
// Serializable decorator
@Serializable()
export class LivingBeing {
/** The living being id (PK) */
@JsonProperty() id: number;
}
// Create a serializable class that extends LivingBeing: Human
@Serializable()
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 name: string,
public id: number,
@JsonProperty() public gender: Gender,
/** This comment works */
@JsonProperty() public readonly birthDate: Date
) {
super();
this.id = id;
}
}
// Create a serializable class: PhoneNumber
@Serializable()
export class PhoneNumber {
@JsonProperty() countryCode: string;
@JsonProperty() value: string;
}
// Create a serializable class that extends Human: Employee
@Serializable()
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({
predicate: 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 serializable class: Animal
@Serializable()
export class Animal {
@JsonProperty() id: number;
@JsonProperty() name: string;
@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) {
this.name = name;
}
}
// Create a serializable class that extends Animal (which extends LivingBeing): Panther
@Serializable()
export class Panther extends Animal {
@JsonProperty() color: string;
// JsonProperty directly inside the constructor
// for property parameters
public constructor(
name: string,
@JsonProperty() public isSpeckled: boolean
) {
super(name);
}
}
// Create a serializable class that extends Animal
// (which extends LivingBeing): Snake
@Serializable()
export class Snake extends Animal {
@JsonProperty() isPoisonous: boolean;
public constructor(name: string) {
super(name);
}
}
// Create a serializable empty class that extends Animal
// (which extends LivingBeing): UnknownAnimal
@Serializable()
export class UnknownAnimal extends Animal {
public constructor(name: string) {
super(name);
}
}
// Create a serializable 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;
};
@Serializable()
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 };
// Array of none-basic type elements
@JsonProperty({ type: Employee })
employees: Array<Employee>;
@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', predicate: snakeOrPanther })
animals: Array<Animal>;
// Property that can be Panther or Snake type
// Use again the predicate function
@JsonProperty({ predicate: snakeOrPanther })
mascot: Panther | Snake;
// Array of empty child classes
@JsonProperty({ type: UnknownAnimal })
unknownAnimals: Array<UnknownAnimal>;
// Dictionary of PhoneNumber
@JsonProperty({ type: PhoneNumber, isDictionary: true })
phoneBook: {[id: string]: PhoneNumber};
// Property which will be not serialized and deserialized
// but event accessible and editable from Zoo class.
public isFree: boolean = true;
public constructor() { }
}
// Create a serializable class that extends Society: Organization
const prefixWithUnderscore = (propertyName: string) => `_${propertyName}`
// Instead of defining the JsonProperty name for each property
// just use a function to do it for all of them.
// Warning: The properties of the base class will be formatted as well
@Serializable({ formatPropertyNames: prefixWithUnderscore })
export class Organization extends Society {
// Override `formatPropertyNames`
@JsonProperty({ name: 'zoos', type: Zoo }) zoos: Array<Zoo>;
// 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({
names: [
'_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>;
}
// Create a serializable class: Society
@Serializable()
export class Organization {
@JsonProperty() id: string;
@JsonProperty() name: string;
}
Json data
// data.ts
export const data: any = {
_id: '1',
_name: 'Zoos Organization',
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,
name: 'Jolene',
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: [
{
name: null
}
],
phoneBook: {
'1': {
value: '111-111-1111'
},
'2': {
value: '222-222-2222'
},
'3': {
value: '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
};
Serialize & Deserialize
// Import functions from library
import { deserialize, serialize } from 'typescript-json-serializer';
import { json } from '../json/data';
import { Organization } from '../models/organization';
// deserialize
const organization = deserialize<Organization>(json, Organization);
// serialize
const data = serialize(organization);
// or
const data = serialize(organization, false);
Using with Create React App
If you are using CRA to create your React App you will need to add a custom configuration in order to add Decorator
and Metadata
features (not supported by React) using customize-cra and react-app-rewired.
First, don't forget to add emitDecoratorMetadata
and experimentalDecorators
inside the tsconfig.json
file (explain in the Installation section).
Next install the dependencies to override the React build config:
npm install -D customize-cra react-app-rewired
# or
yarn add -D customize-cra react-app-rewired
Replace the scripts using react-scripts
in the package.json
file by react-app-rewired
:
// example
{
...
"scripts": {
...
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
...
},
...
}
Install dependencies to add support for Decorator
and Metadata
:
npm install -D @babel/plugin-proposal-decorators \
@babel/preset-typescript \
babel-plugin-parameter-decorator \
babel-plugin-transform-typescript-metadata
# or
yarn add -D @babel/plugin-proposal-decorators \
@babel/preset-typescript \
babel-plugin-parameter-decorator \
babel-plugin-transform-typescript-metadata
Create the config-overrides.js
file in the root of your project
with the following content:
const {
override,
addDecoratorsLegacy,
addBabelPlugin,
addBabelPreset,
} = require("customize-cra");
module.exports = override(
addDecoratorsLegacy(),
addBabelPlugin("babel-plugin-parameter-decorator"),
addBabelPlugin("babel-plugin-transform-typescript-metadata"),
addBabelPreset(["@babel/preset-typescript"])
);
Test
npm run test
# or
yarn test
Author
Gillian Pérard - @GillianPerard
Contributors
- Hyeonsoo David Lee - @civilizeddev