我从对REST服务器的AJAX调用中接收到一个JSON对象。这个对象的属性名与我的TypeScript类相匹配(这是这个问题的后续)。

初始化它的最佳方法是什么?我不认为这将工作,因为类(& JSON对象)的成员是对象的列表和成员是类,而这些类的成员是列表和/或类。

但我更喜欢一种方法,查找成员名和分配他们,创建列表和实例化类的需要,所以我不必为每个类中的每个成员写显式代码(有很多!)


当前回答

另一种选择是使用工厂

export class A {

    id: number;

    date: Date;

    bId: number;
    readonly b: B;
}

export class B {

    id: number;
}

export class AFactory {

    constructor(
        private readonly createB: BFactory
    ) { }

    create(data: any): A {

        const createB = this.createB.create;

        return Object.assign(new A(),
            data,
            {
                get b(): B {

                    return createB({ id: data.bId });
                },
                date: new Date(data.date)
            });
    }
}

export class BFactory {

    create(data: any): B {

        return Object.assign(new B(), data);
    }
}

https://github.com/MrAntix/ts-deserialize

像这样使用

import { A, B, AFactory, BFactory } from "./deserialize";

// create a factory, simplified by DI
const aFactory = new AFactory(new BFactory());

// get an anon js object like you'd get from the http call
const data = { bId: 1, date: '2017-1-1' };

// create a real model from the anon js object
const a = aFactory.create(data);

// confirm instances e.g. dates are Dates 
console.log('a.date is instanceof Date', a.date instanceof Date);
console.log('a.b is instanceof B', a.b instanceof B);

保持类简单 工厂可灵活注射

其他回答

上面描述的第4个选项是一种简单而漂亮的方法,它必须与第二个选项相结合,在这种情况下,您必须处理一个类层次结构,例如成员列表,它是成员超类的任何一个子类,例如Director extends member或Student extends member。在这种情况下,你必须以json格式给出子类类型

另一种选择是使用工厂

export class A {

    id: number;

    date: Date;

    bId: number;
    readonly b: B;
}

export class B {

    id: number;
}

export class AFactory {

    constructor(
        private readonly createB: BFactory
    ) { }

    create(data: any): A {

        const createB = this.createB.create;

        return Object.assign(new A(),
            data,
            {
                get b(): B {

                    return createB({ id: data.bId });
                },
                date: new Date(data.date)
            });
    }
}

export class BFactory {

    create(data: any): B {

        return Object.assign(new B(), data);
    }
}

https://github.com/MrAntix/ts-deserialize

像这样使用

import { A, B, AFactory, BFactory } from "./deserialize";

// create a factory, simplified by DI
const aFactory = new AFactory(new BFactory());

// get an anon js object like you'd get from the http call
const data = { bId: 1, date: '2017-1-1' };

// create a real model from the anon js object
const a = aFactory.create(data);

// confirm instances e.g. dates are Dates 
console.log('a.date is instanceof Date', a.date instanceof Date);
console.log('a.b is instanceof B', a.b instanceof B);

保持类简单 工厂可灵活注射

你可以使用Object。我不知道这是什么时候添加的,我目前使用的是Typescript 2.0.2,这似乎是ES6的一个特性。

client.fetch( '' ).then( response => {
        return response.json();
    } ).then( json => {
        let hal : HalJson = Object.assign( new HalJson(), json );
        log.debug( "json", hal );

这是HalJson

export class HalJson {
    _links: HalLinks;
}

export class HalLinks implements Links {
}

export interface Links {
    readonly [text: string]: Link;
}

export interface Link {
    readonly href: URL;
}

这是chrome说的

HalJson {_links: Object}
_links
:
Object
public
:
Object
href
:
"http://localhost:9000/v0/public

你可以看到它没有递归地赋值

TLDR: typejjson(概念的工作证明)


这个问题复杂的根源在于,我们需要在运行时使用只存在于编译时的类型信息反序列化JSON。这要求类型信息在运行时以某种方式可用。

幸运的是,这个问题可以用装饰器和ReflectDecorators以一种非常优雅和健壮的方式解决:

在受序列化影响的属性上使用属性装饰器,以记录元数据信息并将该信息存储在某个地方,例如在类原型上 将此元数据信息提供给递归初始化器(反序列化器)

 

记录类型信息

结合使用reflectdecorator和属性decorator,可以很容易地记录关于属性的类型信息。这种方法的基本实现是:

function JsonMember(target: any, propertyKey: string) {
    var metadataFieldKey = "__propertyTypes__";

    // Get the already recorded type-information from target, or create
    // empty object if this is the first property.
    var propertyTypes = target[metadataFieldKey] || (target[metadataFieldKey] = {});

    // Get the constructor reference of the current property.
    // This is provided by TypeScript, built-in (make sure to enable emit
    // decorator metadata).
    propertyTypes[propertyKey] = Reflect.getMetadata("design:type", target, propertyKey);
}

对于任何给定的属性,上面的代码段将向类原型上隐藏的__propertyTypes__属性添加该属性的构造函数的引用。例如:

class Language {
    @JsonMember // String
    name: string;

    @JsonMember// Number
    level: number;
}

class Person {
    @JsonMember // String
    name: string;

    @JsonMember// Language
    language: Language;
}

就这样,我们在运行时获得了所需的类型信息,现在可以对其进行处理了。

 

处理类型信息

我们首先需要使用JSON获取一个Object实例。解析——之后,我们可以遍历__propertyTypes__(上面收集的)中的整个对象,并相应地实例化所需的属性。必须指定根对象的类型,以便反序列化程序有一个起始点。

同样,这个方法的一个非常简单的实现是:

function deserialize<T>(jsonObject: any, Constructor: { new (): T }): T {
    if (!Constructor || !Constructor.prototype.__propertyTypes__ || !jsonObject || typeof jsonObject !== "object") {
        // No root-type with usable type-information is available.
        return jsonObject;
    }

    // Create an instance of root-type.
    var instance: any = new Constructor();

    // For each property marked with @JsonMember, do...
    Object.keys(Constructor.prototype.__propertyTypes__).forEach(propertyKey => {
        var PropertyType = Constructor.prototype.__propertyTypes__[propertyKey];

        // Deserialize recursively, treat property type as root-type.
        instance[propertyKey] = deserialize(jsonObject[propertyKey], PropertyType);
    });

    return instance;
}
var json = '{ "name": "John Doe", "language": { "name": "en", "level": 5 } }';
var person: Person = deserialize(JSON.parse(json), Person);

上面的想法有一个很大的优点,即按预期的类型(对于复杂/对象值)反序列化,而不是按JSON中呈现的内容反序列化。如果期望Person,则创建的是Person实例。通过对基本类型和数组采取一些额外的安全措施,可以使这种方法变得安全,从而抵抗任何恶意JSON。

 

边界情况

然而,如果您现在对解决方案如此简单感到高兴,那么我有一些坏消息要告诉您:还有大量的边缘情况需要处理。只有一些是:

数组和数组元素(特别是在嵌套数组中) 多态性 抽象类和接口 ...

如果你不想摆弄所有这些(我打赌你不想),我很乐意推荐一个使用这种方法的概念验证的实验版本typejjson——我创建它就是为了解决这个问题,我自己每天都要面对的问题。

由于decorator仍被认为是实验性的,我不建议在生产中使用它,但到目前为止它对我来说很有用。

对于简单的对象,我喜欢这个方法:

class Person {
  constructor(
    public id: String, 
    public name: String, 
    public title: String) {};

  static deserialize(input:any): Person {
    return new Person(input.id, input.name, input.title);
  }
}

var person = Person.deserialize({id: 'P123', name: 'Bob', title: 'Mr'});

利用在构造函数中定义属性的能力可以使其简洁。

这将为您提供一个类型化对象(与所有使用object的答案相比)。赋值或一些变量,给你一个对象),不需要外部库或装饰器。