• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

appvision-gmbh/json2typescript: Convert JSON to TypeScript with secure type chec ...

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称:

appvision-gmbh/json2typescript

开源软件地址:

https://github.com/appvision-gmbh/json2typescript

开源编程语言:

TypeScript 99.3%

开源软件介绍:

npm

json2typescript

In Angular applications, everyone consumes JSON API's from an external source. Type checking and object mapping is only possible in TypeScript, but not in the JavaScript runtime. As the API may change at any point, it is important for larger projects to verify the consumed data.

json2typescript is a small package containing a helper class that maps JSON objects to an instance of a TypeScript class. After compiling to JavaScript, the result will still be an instance of this class. One big advantage of this approach is, that you can also use methods of this class.

With json2typescript, only a simple function call is necessary, as demonstrated in this TypeScript snippet:

// Assume that you have a class named User defined at some point
// Assume that you get a JSON string from a webservice
let jsonStr: string = ...;
let jsonObj: any = JSON.parse(jsonStr);

// Now you can map the json object to the TypeScript object automatically
let jsonConvert: JsonConvert = new JsonConvert();
let user: User = jsonConvert.deserializeObject(jsonObj, User);
console.log(user); // prints User{ ... } in JavaScript runtime, not Object{ ... }

Tip: All serialize() and deserialize() methods may throw an Error in case of failure. Make sure you catch errors in production!


Changelog

See the changelog in the separate file for bug fixes, new features and breaking changes: Changelog

Warning: If you are reading this document on GitHub, it might be ahead of the published NPM version. Please refer to the ReadMe on NPM if in doubt.

Warning: We earlier suggested to use the @JsonObject(classIdentifier) decorator, but did not enforce it. Since v1.4.0, it is mandatory to use a unique classIdentifier for every class in order to make (de)serialization work properly with class inheritance. In versions above v1.2.0 and below v1.4.0, it is possible to run into issues when not using the decorator.


Getting started

Requirements

We developed json2typescript for Angular 2+ and Ionic 2+. In this document, we only cover this use case. However, you may use our package for pure TypeScript or even JavaScript applications.

Setup a Test Application

We recommend to use the official angular cli tool in order to set up a new Angular project. Then, all you need to do is type the following into your operating system's terminal:

ng new testApplication
cd testApplication

npm install json2typescript

Our package makes use of TypeScript decorators. If not done already, please activate them in your tsconfig.json under compilerOptions as follows:

{
  "compilerOptions": {
[
  ...
]
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
[...]
}

Tip: We have tried to make the compiler options of json2typescript to be as strict as possible. This enables you to use compiler options such as "strictNullChecks": true or "noImplicitAny": true in your own project.

Now you are ready to use the package.

Mapping example

In order to use the json2typescript package, all you need to do is write decorators and import the package. The following things need to be done if you would like to map JSON to existing classes:

  • Classes need to be preceded by @JsonObject(classIdentifier) where the classIdentifier is unique in the whole project
  • Properties need to be preceded by @JsonProperty(jsonProperty, conversionOption, convertingMode)
  • Properties need to have a default value (or undefined), otherwise the mapper will not work

See below an example, so you can learn from it how json2typescript works best.

Assuming that you have created the testApplication in the step before and installed json2typescript as suggested, create a class in a new file city.ts with the following content:

import { JsonObject, JsonProperty } from "json2typescript";

@JsonObject("City") // Make sure "City" is a unique identifier for this class
export class City {

    // This property has no @JsonProperty. 
    // It will not be mapped.
    id: number = 123;

    // This maps the value of the JSON key "name" to the class property "name".
    // If the JSON value is not of type string (or missing), there will be an exception.
    @JsonProperty("name", String)
    name: string = "";

    // This maps the JSON key "founded" to the private class property "_founded".
    // Note the use of public getter and setter.
    // If the JSON value is not of type number (or missing), there will be an exception.
    @JsonProperty("founded", Number)
    private _founded: number = 0;
    get founded() { return this._founded; }

    set founded(value: number) { this._founded = value; }

    // This maps the JSON key "beautiful" to the class property "beautiful".
    // If the JSON value is not of type boolean (or missing), there will be an exception.
    @JsonProperty("beautiful", Boolean)
    beautiful: boolean = false;

    // This maps the JSON key "data" to the class property "data".
    // We are not sure about the type, so we omit the second parameter.
    // There will be an exception if the JSON value is missing.
    @JsonProperty("data") // is the same as @JsonProperty("data", Any)
    data: any = undefined;

    // This maps the JSON key "keywords" to the class property "keywords".
    // This is an example of a string array. Note our syntax "[String]".
    // In the further examples at the end of this document, you can see how to nest complex arrays.
    @JsonProperty("keywords", [String])
    keywords: string[] = []; // or Array<string>

    printInfo() {
        if (this.beautiful)
            console.log(this.name + " was founded in " + this.founded + " and is really beautiful!");
        else
            console.log(this.name + " was founded in " + this.founded + ".");
    }

}

Now create a file country.ts with the following content:

import { City } from "./city";
import { JsonObject, JsonProperty } from "json2typescript";

@JsonObject("Country") // Make sure "Country" is a unique identifier for this class
export class Country {

    // This maps the value of the JSON key "countryName" to the class property "name".
    // If the JSON value is not of type string (or missing), there will be an exception.
    @JsonProperty("countryName", String)
    name: string = "";

    // This maps the value of the JSON key "cities" to the class property "cities".
    // If the JSON value is not of type array object (or missing), there will be an exception.
    // There will be an exception too if the objects in the array do not match the class "City".
    @JsonProperty("cities", [City])
    cities: City[] = [];

}

Then navigate to the file app.component.ts and add the following code:

import { Component, OnInit } from '@angular/core';
import { JsonConvert, OperationMode, ValueCheckingMode } from "json2typescript"
import { Country } from "./country";

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
    ngOnInit() {
        // Define a JSON object (could come from a HTTP service, parsed with JSON.parse() if necessary)
        const jsonObject: any = { 
            "countryName": "Switzerland", 
            "cities": [
                {
                    "id": 1,
                    "name": "Basel",
                    "founded": -200,
                    "beautiful": true,
                    "data": 123,
                    "keywords": ["Rhine", "River"]
                },
                {
                    "id": 1,
                    "name": "Zurich",
                    "founded": 0,
                    "beautiful": false,
                    "data": "no",
                    "keywords": ["Limmat", "Lake"]
                }
            ]
        };

        // Choose your settings
        // Check the detailed reference in the chapter "JsonConvert class properties and methods"
        let jsonConvert: JsonConvert = new JsonConvert();
        jsonConvert.operationMode = OperationMode.LOGGING; // print some debug data
        jsonConvert.ignorePrimitiveChecks = false; // don't allow assigning number to string etc.
        jsonConvert.valueCheckingMode = ValueCheckingMode.DISALLOW_NULL; // never allow null

        // Map to the country class
        let country: Country;
        try {
            country = jsonConvert.deserializeObject(jsonObject, Country);
            country.cities[0].printInfo(); // prints: Basel was founded in -200 and is really beautiful!
        } catch (e) {
            console.log((<Error>e));
        }
    }

Play around with the JSON to provocate exceptions when deserializing the object.

Important notes

Avoid circular dependencies on the classes that use json2typescript. Even if you don't have any errors in your IDE, json2typescript will not properly work in this case.


Detailed reference

Class and property decorators

Decorators should be used whenever you would like to map JSON with TypeScript data. As of now, you must not use more than one decorator per class or property.

Class decorators

The class decorators are used infront of the class declaration and do support one parameter:

@JsonObject("User")
export class User {}

Warning: The class decorator uses the parameter to identify the class. Please use a unique identifier for each class in your project, for example simply the name of the class.

Tip: Make sure you import JsonObject from json2typescript.

First parameter: classIdentifier

The first parameter of @JsonObject must be a unique class identifier, usually just the class name.

Note: This class identifier may be used for automatic instantiation when enabling the discriminator feature.

Property decorators

Property decorators are a vital part for type checking. It is important that the type in the decorator matches the TypeScript type.

For class properties to be visible to the mapper they must be initialized, otherwise they are ignored. They can be initialized using any (valid) value or undefined. See the example below for better understanding:

@JsonObject("User")
export class User {
    
    // A correct example
    @JsonProperty("name", String, false)
    name: string = "";
    
    // An incorrect example
    @JsonProperty("alias", string, false) // Wrong type: Must use String instead.
    alias: string = "";
  
    // An incorrect example
    @JsonProperty("expertise", String, false)
    expertise: string; // No initialization: Property will be ignored without visible exception
    
}

Important note: You must assign any (valid) value or undefined to your property at initialization, otherwise our mapper does not work and will simply ignore the property. Assigning no value is not the same as assigning undefined in context of json2typescript. Non-initialized properties will not trigger any exception, as they are invisible to the mapper.

Tip: Make sure you import JsonObject and JsonProperty from json2typescript.

First parameter: jsonProperty

The first parameter of @JsonProperty is the JSON object property name. It happens that the property names given by the server are very ugly. Here you can map any json property name to the User property name. In our case, json["jsonPropertyName"] gets mapped to user.name.

Second parameter (optional): conversionOption

The second parameter of @JsonProperty describes what happens when doing the mapping between JSON and TypeScript objects. This parameter is optional; the default value is Any (which means no type check is done when the mapping happens).

Use of expected type

If you would like that json2typescript performs an automatic type check according to given TypeScript types, you can pass a type you expect. Follow the following guide when doing that:

  • Make sure you pass the class name and not an instance of the class.
  • In case of primitive types, you have to use the upper case names.
  • In case of any type, import from json2typescript the class Any.

See the following cheat sheet for reference:

Expected type TypeScript type
String string
Number number
Boolean boolean
User User
Any any
[String] string[]
[Number] number[]
[Boolean] boolean[]
[User] User[]
[Any] any[]

At first, our array notation on the left looks odd. But this notation allows you to define even nested arrays. See the examples at the end of this document for more info about nesting arrays.

Tip: It is possible to lazy-load custom classes to prevent circular dependencies. Then, the expected type for custom classes can be written as a string. Please read further below how to implement this feature.

Adding a custom converter

More advanced users may need to use custom converters. If you want json2typescript to use your custom converter, you need to follow these steps:

  • Write a converter class that implements JsonCustomConvert<T> where <T> is the type resulting in the TypeScript class.
  • Make sure you add the @JsonConverter decorator to this class (see next chapter how).
  • Pass your converter class as second param in the @JsonProperty decorator

Assume you would like to transform 1893-11-15 (string from JSON) to a TypeScript Date instance, then you would write a class DateConverter (see next chapter how) and pass it as second param in @JsonProperty as below:

@JsonObject("User")
export class User {
    @JsonProperty("birthdate", DateConverter)
    birthdate: Date = new Date();
}

Third parameter (optional): convertingMode

The third parameter of @JsonProperty determines how nullable property types should be serialized and deserialized. Nullable types are either missing (in JSON), undefined (in TypeScript) or null (both). This parameter is optional; the default value is PropertyConvertingMode.MAP_NULLABLE.

See also the property propertyConvertingMode of the JsonConvert instance.

The values should be used as follows:

  • PropertyConvertingMode.MAP_NULLABLE: the mapper is applied, type is checked
  • PropertyConvertingMode.IGNORE_NULLABLE: the mapper is not applied if the property is missing, undefined or null; the property is not added to the result
  • PropertyConvertingMode.PASS_NULLABLE: the mapper is not applied if the property is missing, undefined or null; the property is added with its value to the result

Tip: Make sure you import the ENUM PropertyConvertingMode when assigning a value to this property.

Handling null, undefined and absent values

Be careful when handling special values as null, undefined and absent properties.

By default, json2typescript throws an exception if a decorated class property cannot be found in the given JSON when deserializing. If you set the third parameter to IGNORE_NULLABLE or PASS_NULLABLE, there will be no exception when it is missing. The type of a property will only be checked if the property is present in the JSON and not undefined or null.

The global setting of valueCheckingMode determines whether you want to allow null values for objects or properties. We recommend to use the most strict option and also set your TypeScript compiler to the strict mode.

The following table explains the difference between the three property converting modes:

ALLOW_NULL serialize(null) serialize(undefined) deserialize(null) deserialize(undefined)
MAP_NULLABLE null error null error
IGNORE_NULLABLE undefined (missing) undefined (missing) default value default value
PASS_NULLABLE null undefined (missing) null undefined
DISALLOW_NULL serialize(null) serialize(undefined) deserialize(null) deserialize(undefined)
MAP_NULLABLE error error error error
IGNORE_NULLABLE undefined (missing) undefined (missing) default value default value
PASS_NULLABLE null undefined (missing) null undefined

As shown in this table, a property with the default setting MAP_NULLABLE is never allowed to be undefined (or missing). The valueCheckingMode determines, whether null is allowed.

Tip: If you want undefined to be treated in the same way as null values, you may set the instance mapUndefinedToNull property to true.

Important notes

  • Make sure you define the expected type as accurate as possible, even if you expect primitive types.
  • By default, casting primitives into other primitives is not allowed. Check the public properties below in this document to change this behaviour.
  • By default, primitives are not allowed to be null. Check the public properties below in this document to change this.
  • If you don't know the type, you may use Any as expected type. You may also omit the second parameter of @JsonProperty.

More about the array syntax

  • You can allow arrays by using the bracket notation combined with the types as seen above. For example: [String] for a string array
  • Mixing arrays is allowed. For example: [String, Number] for an array where the first entry is a string, the second is a number.
  • If the real array is longer than indicated here, the last type will be forced to the rest of the array entries ( above: Number).
  • This means, [String, Number] is equivalent to [String, Number, Number] and so on.
  • Nesting arrays and objects are allowed. For example: [[String, Number], User].
  • This is equivalent to [[String, Number, Number], User, User] and so on.
  • As further example, [[String]] is equal to [[String],[String]] which is equal to [[String,String], [String,String]] and so on.
  • If an array has less elements as given in the expected type, no exception is thrown.
  • For example, if we define [String] or the equivalent [String, String] no exception is thrown - even if the JSON gives us an empty array.

Tip: See the examples at the end of this document for advanced examples for nesting arrays.

Custom converter decorators

In some cases, you may need to make custom conversion between JSON objects and TypeScript objects. You can define custom converters like this:

@JsonConverter
class DateConverter implements JsonCustomConvert<Date> {
    serialize(date: Date): any {
        return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
    }

    deserialize(date: any): Date {
        return new Date(date);
    }
}

Tip: Make sure that you import JsonConverter from json2typescript. Also don't forget to use the same type between the brackets <>, as the serialize() param and deserialize() return value.

Assume that in your JSON you have a date in a standardized format, such as 2017-07-19 10:00:00. You could use the custom converter class above to make sure it is stored as a real TypeScript Date in your class. For your property, you simply have use the @JsonProperty decorator as follows:


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap