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

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

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


当前回答

也许不现实,但简单的解决方案:

interface Bar{
x:number;
y?:string; 
}

var baz:Bar = JSON.parse(jsonString);
alert(baz.y);

对困难的依赖也要努力!!

其他回答

**model.ts**
export class Item {
    private key: JSON;
    constructor(jsonItem: any) {
        this.key = jsonItem;
    }
}

**service.ts**
import { Item } from '../model/items';

export class ItemService {
    items: Item;
    constructor() {
        this.items = new Item({
            'logo': 'Logo',
            'home': 'Home',
            'about': 'About',
            'contact': 'Contact',
        });
    }
    getItems(): Item {
        return this.items;
    }
}

我的方法略有不同。我没有将属性复制到新的实例中,我只是改变了现有pojo的原型(在旧的浏览器上可能不太好用)。每个类负责提供一个setprototype方法来设置任何子对象的原型,这些子对象反过来提供它们自己的setprototype方法。

(我也使用_Type属性来获取未知对象的类名,但在这里可以忽略)

class ParentClass
{
    public ID?: Guid;
    public Child?: ChildClass;
    public ListOfChildren?: ChildClass[];

    /**
     * Set the prototypes of all objects in the graph.
     * Used for recursive prototype assignment on a graph via ObjectUtils.SetPrototypeOf.
     * @param pojo Plain object received from API/JSON to be given the class prototype.
     */
    private static SetPrototypes(pojo: ParentClass): void
    {
        ObjectUtils.SetPrototypeOf(pojo.Child, ChildClass);
        ObjectUtils.SetPrototypeOfAll(pojo.ListOfChildren, ChildClass);
    }
}

class ChildClass
{
    public ID?: Guid;
    public GrandChild?: GrandChildClass;

    /**
     * Set the prototypes of all objects in the graph.
     * Used for recursive prototype assignment on a graph via ObjectUtils.SetPrototypeOf.
     * @param pojo Plain object received from API/JSON to be given the class prototype.
     */
    private static SetPrototypes(pojo: ChildClass): void
    {
        ObjectUtils.SetPrototypeOf(pojo.GrandChild, GrandChildClass);
    }
}

下面是ObjectUtils.ts:

/**
 * ClassType lets us specify arguments as class variables.
 * (where ClassType == window[ClassName])
 */
type ClassType = { new(...args: any[]): any; };

/**
 * The name of a class as opposed to the class itself.
 * (where ClassType == window[ClassName])
 */
type ClassName = string & {};

abstract class ObjectUtils
{
/**
 * Set the prototype of an object to the specified class.
 *
 * Does nothing if source or type are null.
 * Throws an exception if type is not a known class type.
 *
 * If type has the SetPrototypes method then that is called on the source
 * to perform recursive prototype assignment on an object graph.
 *
 * SetPrototypes is declared private on types because it should only be called
 * by this method. It does not (and must not) set the prototype of the object
 * itself - only the protoypes of child properties, otherwise it would cause a
 * loop. Thus a public method would be misleading and not useful on its own.
 * 
 * https://stackoverflow.com/questions/9959727/proto-vs-prototype-in-javascript
 */
public static SetPrototypeOf(source: any, type: ClassType | ClassName): any
{
    let classType = (typeof type === "string") ? window[type] : type;

    if (!source || !classType)
    {
        return source;
    }

    // Guard/contract utility
    ExGuard.IsValid(classType.prototype, "type", <any>type);

    if ((<any>Object).setPrototypeOf)
    {
        (<any>Object).setPrototypeOf(source, classType.prototype);
    }
    else if (source.__proto__)
    {
        source.__proto__ = classType.prototype.__proto__;
    }

    if (typeof classType["SetPrototypes"] === "function")
    {
        classType["SetPrototypes"](source);
    }

    return source;
}

/**
 * Set the prototype of a list of objects to the specified class.
 * 
 * Throws an exception if type is not a known class type.
 */
public static SetPrototypeOfAll(source: any[], type: ClassType): void
{
    if (!source)
    {
        return;
    }

    for (var i = 0; i < source.length; i++)
    {
        this.SetPrototypeOf(source[i], type);
    }
}
}

用法:

let pojo = SomePlainOldJavascriptObjectReceivedViaAjax;

let parentObject = ObjectUtils.SetPrototypeOf(pojo, ParentClass);

// parentObject is now a proper ParentClass instance

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仍被认为是实验性的,我不建议在生产中使用它,但到目前为止它对我来说很有用。

也许不现实,但简单的解决方案:

interface Bar{
x:number;
y?:string; 
}

var baz:Bar = JSON.parse(jsonString);
alert(baz.y);

对困难的依赖也要努力!!

我发现最适合这个目的的是类转换器

这就是它的用法:

一些类:

export class Foo {

    name: string;

    @Type(() => Bar)
    bar: Bar;

    public someFunction = (test: string): boolean => {
        ...
    }
}

// the docs say "import [this shim] in a global place, like app.ts" 
import 'reflect-metadata';

// import this function where you need to use it
import { plainToClass } from 'class-transformer';

export class SomeService {

  anyFunction() {
    u = plainToClass(Foo, JSONobj);
  }
}

如果使用@Type装饰器,也会创建嵌套属性。