📜 ⬆️ ⬇️

Checking JSON using decorators in TypeScript

I really like static types, so TypeScript has become an indispensable helper when working with NodeJS or browser-based JS.

On duty, you have to have a lot of things to do with JSON, and here the TypeScript type system does not help anything, it even hinders, because the compiler reports that there are no errors, JSON.parse returns the type Any. In addition, TypeScript does not support reflection, due to the specifics of the work, which means that it is not possible to check the type based on the already existing code. Also, until recently, there were no tools for meta-programming at all.

Often, checking the correctness of the incoming JSON object is wrapped in huge code in class constructors, or in the same configuration files. But finally , decorators appeared in TypeScript 1.5 .
')
Decorators allow you to perform certain manipulations with a class, method, property, or parameter during their declaration , while it is possible to transfer additional information about the object being decorated. This I took advantage of.

It looks like this:

class X extends Model { @prop({ type: PropType.Array, arrayProp: { type: PropType.Number } }) a; } enum Enum1 { V1, V2, V3 } class MyClass extends Model { @prop({ type: PropType.Object, class: X }) prop1; @prop({ type: PropType.String }) propString; @prop({ type: PropType.Enum, class: Enum1 }) b: Enum1; } class TestString extends Model { @prop({ type: PropType.String }) prop: string; } 

It turned out a little duplication when specifying the type, but it is much less evil than describing the class system separately, purely for type checking.

Task


We need to get from the JSON object a full TypeScript object with no errors in the fields, with hierarchical support for nested objects (with its own types). I did not implement working with JSON, in the root of which is not a plain-object; there was no need.

The main types I wanted to get at the output of JSON:

Decision


To implement, I created a property decorator and a special Model class that all classes whose fields we want to check must inherit. When defining a class, information about all fields declared with the decorator (type, mandatory, etc.) is entered in a special field. By the way, you can not use inheritance, but create a class decorator (for example, @jsonable).

String - just checked on the actual JS-object String. Number and Boolean have the ability to specify the isCasting parameter, in which case it is checked whether the value can be converted to a number or perhaps any value for Boolean.

Object - checks whether the object is plain-object and if a special class parameter is specified, then an instance is created, and if the class inherits the Model, then its fields are also checked.
Array - check for JS-Array + ability to specify the type of enumerated values. Each value is checked for type separately.
Enum - if the class parameter is specified (in this case, a reference to enum), then it is checked whether the necessary value exists in this enumeration. In JSON, specified by text (as in the definition of enum).

I will not give the code here, it is quite simple, I put it on github . Here is an example of use for the classes described in the listing above:

 var a = new MyClass({ propString: "test1", prop1: { a: [1, 2, 3] }, b: "V1" }); console.assert(a.prop1 instanceof X); console.assert(ab === Enum1.V1, "Invalid JSON"); console.assert(a.propString === "test1"); try { new TestString({ prop: 123 }); console.assert(false, "Not check string field"); } catch (e) { } try { new MyClass({}); console.assert(false, "Not check required field"); } catch (e) { } 

As I have already said, this is only a prototype so far, I have not participated in the combat mode, but it gives rise to similar solutions or the possibility of using decorators.

PS Recently on Habré was published an article about the simplicity of Go - "It is difficult about the simplicity of Go . " So, the new key character @ in TypeScript has already allowed me to reduce the amount of code at times, and I remembered it right away. And how happy I was with the arrow-function! I look forward to async / await (yes, yes, 2 new words) that will get rid of tons of then, when, resolve, reject, etc.

Source: https://habr.com/ru/post/262105/


All Articles