Hi, Habr! I want to share with you my library for deserializing JSON objects into classes, which also automatically validates input data by type.
Not so long ago, such a wonderful thing as classes appeared in JavaScript, which greatly simplified the process of writing code. But unfortunately, there is no functionality for deserializing JSON into these same classes, i.e. You can serialize a class into a string, but you can do it yourself. And now, to correct this deficiency, the
ts-serializable library was written, which I want to share with you.
What is the essence of the problem shows the following code:
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();
What is the reason for the mistakes of the new Ivan? The fact is that the JSON.parse method deserializes not into the User class, but into the Object class, which simply does not have the getFullName and getAge methods.
')
How does my library help solve this problem and deserialize into User, not Object? It is enough just to slightly modify the code:
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; //
Everything is very simple. We inherit our class from the Serializable class, which has two fromJson methods for deserialization and toJSON for serialization, and the properties are hung by the @jsonProperty decorator with an indication of the data types that are allowed to be received from JSON. Empty data will be ignored, a warning will be issued to the console, and the default value will remain in the property.
That's all that's all. Now on the front, you can deserialize and serialize as easily as it does in C #, Java, and other languages. The basis is the behavior of Newtonsoft Json.NET.
FAQ
Why inherit from Serializable?In order to add two methods fromJson and toJSON to the model. You can do the same thing through a decorator or monkey patching. But inheritance is a better method for typescript.
How data validation occursIn the decorator you need to assign the constructor of the data types that are allowed to receive from JSON. Objects Boolean, String, Number will be given, respectively, boolean, string, number. If you need to accept an array, the type is framed by array brackets, for example @jsonProperty ([String]). If the constructor is inherited from the Serializable class, it will also be deserialized into the class, if not, the object will be returned.
How to catch validation errors?By default, the library simply writes warnings to the validation error console. To override this behavior, for example, to throw exceptions or logging to the backend, you must override the model's onWrongType method.
Bonus 1. Deep copy.
const user1 = new Uesr(); const user2 = new User().fromJson(user1);
Bonus 2. Lazy ViewModels.
If you need to create a model with additional data, for example, for a view, but which does not accept the backend, you can simply extend the model with new properties and mark these properties with the @jsonIgnore decorator. And then these properties will not be serialized.
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());