我从对 REST 服务器的 AJAX 调用接收到一个 JSON 对象。该对象具有与我的 TypeScript 类匹配的属性名称(这是此问题的后续内容)。
初始化它的最佳方法是什么?我认为这不会起作用,因为类(和 JSON 对象)的成员是对象列表和类成员,而这些类的成员是列表和/或类。
但我更喜欢一种查找成员名称并分配它们的方法,根据需要创建列表和实例化类,因此我不必为每个类中的每个成员编写显式代码(有很多!)
这些是一些快速的镜头,以展示几种不同的方式。它们绝不是“完整的”,作为免责声明,我认为这样做不是一个好主意。此外,代码也不是很干净,因为我只是很快地把它一起输入了。
另请注意:当然,可反序列化的类需要具有默认构造函数,就像我知道任何类型的反序列化的所有其他语言一样。当然,如果您调用不带参数的非默认构造函数,Javascript 不会抱怨,但类最好为它做好准备(另外,它不会真的是“打字方式”)。
这种方法的问题主要是任何成员的名称都必须与其类匹配。这会自动将您限制为每个班级只有一个相同类型的成员,并违反了几条良好实践的规则。我强烈建议不要这样做,但只是在这里列出,因为它是我写这个答案时的第一个“草稿”(这也是为什么名字是“Foo”等)。
module Environment { export class Sub { id: number; } export class Foo { baz: number; Sub: Sub; } } function deserialize(json, environment, clazz) { var instance = new clazz(); for(var prop in json) { if(!json.hasOwnProperty(prop)) { continue; } if(typeof json[prop] === 'object') { instance[prop] = deserialize(json[prop], environment, environment[prop]); } else { instance[prop] = json[prop]; } } return instance; } var json = { baz: 42, Sub: { id: 1337 } }; var instance = deserialize(json, Environment, Environment.Foo); console.log(instance);
为了摆脱选项 #1 中的问题,我们需要了解 JSON 对象中的节点是什么类型的某种信息。问题在于,在 Typescript 中,这些东西是编译时构造,我们在运行时需要它们——但运行时对象在设置它们之前根本不知道它们的属性。
一种方法是让类知道他们的名字。不过,您在 JSON 中也需要此属性。实际上,您 只 需要在 json 中使用它:
module Environment { export class Member { private __name__ = "Member"; id: number; } export class ExampleClass { private __name__ = "ExampleClass"; mainId: number; firstMember: Member; secondMember: Member; } } function deserialize(json, environment) { var instance = new environment[json.__name__](); for(var prop in json) { if(!json.hasOwnProperty(prop)) { continue; } if(typeof json[prop] === 'object') { instance[prop] = deserialize(json[prop], environment); } else { instance[prop] = json[prop]; } } return instance; } var json = { __name__: "ExampleClass", mainId: 42, firstMember: { __name__: "Member", id: 1337 }, secondMember: { __name__: "Member", id: -1 } }; var instance = deserialize(json, Environment); console.log(instance);
如上所述,类成员的类型信息在运行时不可用——除非我们使其可用。我们只需要对非原始成员执行此操作,我们就可以开始了:
interface Deserializable { getTypes(): Object; } class Member implements Deserializable { id: number; getTypes() { // since the only member, id, is primitive, we don't need to // return anything here return {}; } } class ExampleClass implements Deserializable { mainId: number; firstMember: Member; secondMember: Member; getTypes() { return { // this is the duplication so that we have // run-time type information :/ firstMember: Member, secondMember: Member }; } } function deserialize(json, clazz) { var instance = new clazz(), types = instance.getTypes(); for(var prop in json) { if(!json.hasOwnProperty(prop)) { continue; } if(typeof json[prop] === 'object') { instance[prop] = deserialize(json[prop], types[prop]); } else { instance[prop] = json[prop]; } } return instance; } var json = { mainId: 42, firstMember: { id: 1337 }, secondMember: { id: -1 } }; var instance = deserialize(json, ExampleClass); console.log(instance);
2016 年 1 月 3 日更新: 正如 @GameAlchemist在评论(想法、实现)中指出的那样,从 Typescript 1.7 开始,下面描述的解决方案可以使用类/属性装饰器以更好的方式编写。
序列化始终是一个问题,在我看来,最好的方法不是最短的方法。在所有选项中,这是我更喜欢的,因为该类的作者可以完全控制反序列化对象的状态。如果我不得不猜测,我会说所有其他选项迟早会给您带来麻烦(除非 Javascript 提供了处理此问题的本地方法)。
真的,下面的例子并没有做到灵活性。它确实只是复制了类的结构。但是,您必须记住的区别在于,该类可以完全控制使用它想要控制整个类的状态的任何类型的 JSON(您可以计算事物等)。
interface Serializable<T> { deserialize(input: Object): T; } class Member implements Serializable<Member> { id: number; deserialize(input) { this.id = input.id; return this; } } class ExampleClass implements Serializable<ExampleClass> { mainId: number; firstMember: Member; secondMember: Member; deserialize(input) { this.mainId = input.mainId; this.firstMember = new Member().deserialize(input.firstMember); this.secondMember = new Member().deserialize(input.secondMember); return this; } } var json = { mainId: 42, firstMember: { id: 1337 }, secondMember: { id: -1 } }; var instance = new ExampleClass().deserialize(json); console.log(instance);