npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@sm2dev/typescript-json-serializer

v6.0.1

Published

Typescript library to serialize classes into json and deserialize json into classes.

Downloads

39

Readme

typescript-json-serializer

npm npm bundle size (version) Coverage Status Known Vulnerabilities

A typescript library to deserialize json into typescript classes and serialize classes into json.

Summary

  1. Installation
  2. Usage
  3. API
  4. Development
  5. Thanks to

Installation

npm install @sm2dev/typescript-json-serializer --save
# or
yarn add @sm2dev/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,
        ...
    }
}

Usage

import { JsonSerializer, throwError } from '@sm2dev/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 '@sm2dev/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: '[email protected]',
                gender: 1,
                phoneNumber: '111-111-1111'
            },
            employees: [
                {
                    employeeId: 1,
                    name: 'Bob Razowsky',
                    birthDate: '1984-04-03T22:00:00.000Z',
                    email: '[email protected]',
                    gender: 1,
                    phoneNumber: '111-111-1111'
                },
                {
                    employeeId: 2,
                    name: 'Mikasa Ackerman',
                    birthDate: '1984-01-11T22:00:00.000Z',
                    email: '[email protected]',
                    gender: 0,
                    phoneNumber: '222-222-2222'
                },
                {
                    employeeId: 3,
                    name: 'Red Redington',
                    birthDate: '1970-12-04T22:00:00.000Z',
                    email: '[email protected]',
                    gender: 1,
                    phoneNumber: '333-333-3333'
                },
                {
                    employeeId: 4,
                    name: 'Fried Richter',
                    birthDate: '1994-04-01T22:00:00.000Z',
                    email: '[email protected]',
                    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: '[email protected]',
                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.

deserialize<T extends object>(
    value: string | object | Array<object>,
    type: Type<T> | T
): T | Array<T|Nullish> | Nullish
Parameters

value
Type: string | object | Array<object>
Optional: false
Description: The value to deserialize.

type
Type: Type<T> | T
Optional: false
Description: The constructor class to deserialize into.

Return

T or Array<T|Nullish> or Nullish


deserializeObject()
To use when the value to deserialize is an object.

deserializeObject<T extends object>(
    obj: string | object,
    type: Type<T> | T
): T | Nullish
Parameters

obj
Type: string | object
Optional: false
Description: The object to deserialize.

type
Type: Type<T> | T
Optional: false
Description: The constructor class to deserialize into.

Return

T or Nullish


deserializeObjectArray()
To use when the value to deserialize is an array.

deserializeObjectArray<T extends object>(
    array: string | Array<any>,
    type: Type<T> | T
): Array<T|Nullish> | Nullish
Parameters

array
Type: string | Array<any>
Optional: false
Description: The object to deserialize.

type
Type: Type<T> | T
Optional: false
Description: The constructor class to deserialize into.

Return

Array<T|Nullish> or Nullish


serialize()
To use when you don't know if the value to serialize is an object or an array

serialize(value: object | Array<object>): object | Array<object|Nullish> | Nullish
Parameters

value
Type: object | Array<object>
Optional: false
Description: The object or the array of objects to serialize.

Return

object or Array<object|Nullish> or Nullish


serializeObject()
To use when the value to serialize is an object.

serializeObject(instance: object): object | Nullish
Parameters

instance
Type: object
Optional: false
Description: The object to serialize.

Return

object or Nullish


serializeObjectArray()
To use when the value to serialize is an array of objects.

serializeObjectArray(array: Array<object>): Array<object|Nullish> | Nullish
Parameters

array
Type: Array<object>
Optional: false
Description: The array of objects to serialize.

Return

Array<object|Nullish> or Nullish

Definitions

Types

JsonObjectOptions
constructorParams?: Array<unknown>;
JsonPropertyOptions
name?: string | Array<string>;
type?: Function | PredicateProto;
dataStructure?: DataStructure;
required?: boolean;
beforeSerialize?: IOProto;
afterSerialize?: IOProto;
beforeDeserialize?: IOProto;
afterDeserialize?: IOProto;
JsonSerializerOptions
errorCallback?: ErrorCallback = logError;
nullishPolicy: NullishPolicy = {
    undefined: 'remove',
    null: 'allow'
};
additionalPropertiesPolicy: Policy = 'remove';
formatPropertyName?: FormatPropertyNameProto;
NullishPolicy
undefined: Policy;
null: Policy;

Value types

DataStructure
'array' | 'dictionary' | 'map' | 'set'
Nullish
null | undefined
Policy
'allow' | 'disallow' | 'remove'

Function types

ErrorCallback
(message: string) => void

The library provide two built-in methods:

  • logError that logs the error.
  • throwError that throws the error.
FormatPropertyNameProto
(propertyName: string) => string;
IOProto
(property: any, currentInstance?: any) => any
PredicateProto
(property: any, parentProperty?: any) => any
Type<T>
new (...args: Array<any>) => T;

Note: represent a constructor.

Development

Prerequisites

Install dependencies

yarn

Run build

yarn build

Run linter

yarn lint

Run tests

yarn test

Thanks to

Author

Gillian Pérard - @GillianPerard

Contributors