TypeScript: Десериализация JSON в классы с валидацией типов у свойств

в 15:26, , рубрики: javascript, TypeScript

Привет! Хочу поделиться с вами своей библиотекой для десериализации объектов JSON в классы, которая еще и автоматически валидирует по типам входные данные.

Не так давно в JavaScript появилась такая замечательная вещь как классы, которая значительно упростила процесс написания кода. Но к сожалению не появился функционал для десериализации JSON в эти самые классы, т.е. сериализовать класс в строку можно, а вот обратно уже своими силами. И вот для исправления этого недостатка и была написана библиотека ts-serializable которой я хочу поделиться с вами.

В чем суть проблемы показывает следующий код:

export class User {
    public firstName: string = "Иван";
    public lastName: string = "Петров";
    public birthDate: Date = new Date();
    
    public getFullName(): string {
        return [this.firstName, this.lastName].join(' ');
    }
 
    public getAge(): number {
        return new Date().getFullYear() - this.birthDate.getFullYear();
    }
}

const ivan = new User();
ivan.getFullName(); // возвращает ФИО
ivan.getAge(); // возвращает возраст
ivan instanceof User; // является инстансом пользователя

const text = JSON.stringify(ivan); // сериализуем класс в текст
const newIvan = JSON.parse(text); // десериализуем обратно

newIvan.getFullName(); // ОшибкаТипа: метод getFullName отсутствует
newIvan.getAge(); // ОшибкаТипа: метод getAge отсутствует
newIvan instanceof User; // не является инстансом пользователя

В чем причина ошибок нового Ивана? Дело в том что метод JSON.parse десериализует не в класс User, а в класс Object у которого просто нету методов getFullName и getAge.

Как же моя библиотека помогает решить эту проблему и десериализовать в User, а не Object? Достаточно просто слегка модифицировать код:

import { jsonProperty, Serializable } from "ts-serializable";
 
export class User extends Serializable {
    @jsonProperty(String)
    public firstName: string = "Иван";
    @jsonProperty(String)
    public lastName: string = "Петров";
    @jsonProperty(Date)
    public birthDate: Date = new Date();
    
    public getFullName(): string {
        return [this.firstName, this.lastName].join(' ');
    }
 
    public getAge(): number {
        return new Date().getFullYear() - this.birthDate.getFullYear();
    }
}

const ivan = new User();
ivan.getFullName(); // возвращает ФИО
ivan.getAge(); // возвращает возраст
ivan instanceof User; // является инстансом пользователя

const text = JSON.stringify(ivan); // сериализуем класс в текст
const newIvan = new User().fromJson(JSON.parse(text)); // десериализуем обратно в User

newIvan.getFullName(); // возвращает ФИО
newIvan.getAge(); // возвращает возраст
newIvan instanceof User; // является инстансом пользователя

Все очень просто. Наследуем наш класс от класса Serializable у которого есть два метода fromJson для десериализации и toJSON для сериализации, а свойствам вешаем декоратор @jsonProperty с указанием тех типов данных которые разрешено принимать из JSON. Невалидные данные будут проигнорированы, в консоль выдано предупреждение, а в свойстве останется значение по умолчанию.

Вот вообщем то и все. Теперь на фронте можно десериализовать и сериализовать так же просто как это делается в C#, Java и других языках. За основу взято поведение Newtonsoft Json.NET.

FAQ

Для чего наследоваться от Serializable?

Для того что бы добавить в модель два метода fromJson и toJSON. Можно тоже самое реализовать и через декоратор или monkey patching. Но наследование является более правильным методом для Typescript.

Как происходит валидация данных

В декоратор необходимо назначить конструктор тех типов данных которые разрешено принимать из JSON. Объекты Boolean, String, Number выдадут соответственно boolean, string, number. Если вам нужно принять массив, то тип обрамляется скобочками массива, например @jsonProperty([String]). Если конструктор отнаследован от класса Serializable, то оно также будет десериализовано в класс, если нет то вернется объект.

Как отловить ошибки валидации?

По умолчанию библиотеку просто пишет предупреждения в консоль об ошибках валидации. Для переопределения этого поведения, например для бросания исключений или логирования на бекенд, необходимо переопределить метод onWrongType у модели.

Бонус 1. Глубокая копия.

const user1 = new Uesr();
const user2 = new User().fromJson(user1); // создаст глубокую копию

Бонус 2. Ленивые ViewModels.

Если вам необходимо создать модель с дополнительными данными, например для вьюхи, но которые не принимает бекенд, можно просто расширить модель новыми свойствами и пометить эти свойства декоратором @jsonIgnore. И тогда эти свойства не будут сериализованы.

import { jsonProperty, Serializable } from "ts-serializable";

export class User extends Serializable {
    @jsonProperty(String)
    public firstName: string = "Иван";
    @jsonIgnore()
    public isExpanded: boolean = false;
}

JSON.stringify(new User()); // вернет {"firstName":"Иван"}

Автор: LabEG

Источник

* - обязательные к заполнению поля