如何使用 JSON 对象初始化 TypeScript 对象

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22885995/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 17:15:32  来源:igfitidea点击:

How do I initialize a TypeScript object with a JSON object

jsontypescript

提问by David Thielen

I receive a JSON object from an AJAX call to a REST server. This object has property names that match my TypeScript class (this is a follow-on to this question).

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

What is the best way to initialize it? I don't think thiswill work because the class (& JSON object) have members that are lists of objects and members that are classes, and those classes have members that are lists and/or classes.

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

But I'd prefer an approach that looks up the member names and assigns them across, creating lists and instantiating classes as needed, so I don't have to write explicit code for every member in every class (there's a LOT!)

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

采纳答案by Ingo Bürk

These are some quick shots at this to show a few different ways. They are by no means "complete" and as a disclaimer, I don't think it's a good idea to do it like this. Also the code isn't too clean since I just typed it together rather quickly.

这些是一些快速截图,以展示几种不同的方式。它们绝不是“完整的”,作为免责声明,我认为这样做不是一个好主意。代码也不太干净,因为我只是很快地把它输入在一起。

Also as a note: Of course deserializable classes need to have default constructors as is the case in all other languages where I'm aware of deserialization of any kind. Of course, Javascript won't complain if you call a non-default constructor with no arguments, but the class better be prepared for it then (plus, it wouldn't really be the "typescripty way").

另请注意:当然,可反序列化的类需要具有默认构造函数,就像我知道任何类型的反序列化的所有其他语言一样。当然,如果你调用一个没有参数的非默认构造函数,Javascript 不会抱怨,但是类最好为此做好准备(另外,它不会真的是“打字稿方式”)。

Option #1: No run-time information at all

选项#1:根本没有运行时信息

The problem with this approach is mostly that the name of any member must match its class. Which automatically limits you to one member of same type per class and breaks several rules of good practice. I strongly advise against this, but just list it here because it was the first "draft" when I wrote this answer (which is also why the names are "Foo" etc.).

这种方法的问题主要是任何成员的名称都必须与其类匹配。这会自动将您限制为每个班级的一个相同类型的成员,并违反了一些良好实践的规则。我强烈建议不要这样做,但只是在这里列出它,因为它是我写这个答案时的第一个“草稿”(这也是为什么名字是“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);

Option #2: The nameproperty

选项#2:名称属性

To get rid of the problem in option #1, we need to have some kind of information of what type a node in the JSON object is. The problem is that in Typescript, these things are compile-time constructs and we need them at runtime – but runtime objects simply have no awareness of their properties until they are set.

为了摆脱选项 #1 中的问题,我们需要了解 JSON 对象中的节点是什么类型的某种信息。问题是在 Typescript 中,这些东西是编译时构造,我们在运行时需要它们——但运行时对象在设置之前根本不知道它们的属性。

One way to do it is by making classes aware of their names. You need this property in the JSON as well, though. Actually, you onlyneed it in the json:

一种方法是让类知道它们的名字。不过,您在 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);

Option #3: Explicitly stating member types

选项#3:明确说明成员类型

As stated above, the type information of class members is not available at runtime – that is unless we make it available. We only need to do this for non-primitive members and we are good to go:

如上所述,类成员的类型信息在运行时是不可用的——除非我们使它可用。我们只需要为非原始成员执行此操作,我们很高兴:

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);

Option #4: The verbose, but neat way

选项#4:冗长但简洁的方式

Update 01/03/2016:As @GameAlchemist pointed out in the comments (idea, implementation), as of Typescript 1.7, the solution described below can be written in a better way using class/property decorators.

2016 年 1 月 3 日更新:正如@GameAlchemist 在评论(ideaimplementation)中指出的那样,从Typescript 1.7 开始,下面描述的解决方案可以使用类/属性装饰器以更好的方式编写。

Serialization is always a problem and in my opinion, the best way is a way that just isn't the shortest. Out of all the options, this is what I'd prefer because the author of the class has full control over the state of deserialized objects. If I had to guess, I'd say that all other options, sooner or later, will get you in trouble (unless Javascript comes up with a native way for dealing with this).

序列化始终是一个问题,在我看来,最好的方法不是最短的方法。在所有选项中,这是我更喜欢的,因为类的作者可以完全控制反序列化对象的状态。如果我不得不猜测,我会说所有其他选项迟早会给您带来麻烦(除非 Javascript 提出了一种本地方式来处理这个问题)。

Really, the following example doesn't do the flexibility justice. It really does just copy the class's structure. The difference you have to keep in mind here, though, is that the class has full control to use any kind of JSON it wants to control the state of the entire class (you could calculate things etc.).

真的,下面的例子并没有做到灵活性正义。它确实只是复制了类的结构。但是,您在这里必须记住的不同之处在于,该类可以完全控制使用任何类型的 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);

回答by John Weisz

TLDR: TypedJSON(working proof of concept)

TLDR:TypedJSON(工作概念证明)



The root of the complexity of this problem is that we need to deserialize JSON at runtimeusing type information that only exists at compile time. This requires that type-information is somehow made available at runtime.

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

Fortunately, this can be solved in a very elegant and robust way with decoratorsand ReflectDecorators:

幸运的是,这可以通过装饰器ReflectDecorators以一种非常优雅和健壮的方式解决

  1. Use property decoratorson properties which are subject to serialization, to record metadata information and store that information somewhere, for example on the class prototype
  2. Feed this metadata information to a recursive initializer (deserializer)
  1. 在需要序列化的属性上使用属性装饰器,记录元数据信息并将该信息存储在某处,例如在类原型上
  2. 将此元数据信息提供给递归初始化器(解串器)

 

 

Recording Type-Information

录音类型信息

With a combination of ReflectDecoratorsand property decorators, type information can be easily recorded about a property. A rudimentary implementation of this approach would be:

通过ReflectDecorators和属性装饰器的组合,可以轻松记录有关属性的类型信息。这种方法的基本实现是:

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);
}

For any given property, the above snippet will add a reference of the constructor function of the property to the hidden __propertyTypes__property on the class prototype. For example:

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

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

    @JsonMember// Number
    level: number;
}

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

    @JsonMember// Language
    language: Language;
}

And that's it, we have the required type-information at runtime, which can now be processed.

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

 

 

Processing Type-Information

处理类型-信息

We first need to obtain an Objectinstance using JSON.parse-- after that, we can iterate over the entires in __propertyTypes__(collected above) and instantiate the required properties accordingly. The type of the root object must be specified, so that the deserializer has a starting-point.

我们首先需要Object使用JSON.parse--获取一个实例,然后我们可以遍历__propertyTypes__(上面收集的)中的全部内容并相应地实例化所需的属性。必须指定根对象的类型,以便反序列化器有一个起点。

Again, a dead simple implementation of this approach would be:

同样,这种方法的一个简单的实现将是:

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);

The above idea has a big advantage of deserializing by expectedtypes (for complex/object values), instead of what is present in the JSON. If a Personis expected, then it is a Personinstance that is created. With some additional security measures in place for primitive types and arrays, this approach can be made secure, that resists anymalicious JSON.

上述想法在按预期类型(对于复杂/对象值)而不是 JSON 中存在的类型进行反序列化方面具有很大优势。如果 aPerson是预期的,那么它是一个Person创建的实例。通过为原始类型和数组采取一些额外的安全措施,这种方法可以变得安全,可以抵御任何恶意的 JSON。

 

 

Edge Cases

边缘情况

However, if you are now happy that the solution is thatsimple, I have some bad news: there is a vastnumber of edge cases that need to be taken care of. Only some of which are:

但是,如果你现在高兴的是,解决方案简单的,我有一些坏消息:有一个广阔的需要被照顾的边缘情况数。只有其中一些是:

  • Arrays and array elements (especially in nested arrays)
  • Polymorphism
  • Abstract classes and interfaces
  • ...
  • 数组和数组元素(尤其是嵌套数组)
  • 多态性
  • 抽象类和接口
  • ...

If you don't want to fiddle around with all of these (I bet you don't), I'd be glad to recommend a working experimental version of a proof-of-concept utilizing this approach, TypedJSON-- which I created to tackle this exact problem, a problem I face myself daily.

如果你不想摆弄所有这些(我打赌你不想),我很乐意推荐一个使用这种方法的概念验证的工作实验版本,TypedJSON——这是我创建的解决这个确切的问题,这是我每天都面临的问题。

Due to how decorators are still being considered experimental, I wouldn't recommend using it for production use, but so far it served me well.

由于装饰器仍然被认为是实验性的,我不建议将它用于生产用途,但到目前为止它对我很有用。

回答by xenoterracide

you can use Object.assignI don't know when this was added, I'm currently using Typescript 2.0.2, and this appears to be an ES6 feature.

你可以使用Object.assign我不知道这是什么时候添加的,我目前使用的是 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 );

here's HalJson

这是 HalJson

export class HalJson {
    _links: HalLinks;
}

export class HalLinks implements Links {
}

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

export interface Link {
    readonly href: URL;
}

here's what chrome says it is

这是 chrome 所说的

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

so you can see it doesn't do the assign recursively

所以你可以看到它不会递归地分配

回答by André

I've been using this guy to do the job: https://github.com/weichx/cerialize

我一直在使用这个人来完成这项工作:https: //github.com/weichx/cerialize

It's very simple yet powerful. It supports:

它非常简单但功能强大。它支持:

  • Serialization & deserialization of a whole tree of objects.
  • Persistent & transient properties on the same object.
  • Hooks to customize the (de)serialization logic.
  • It can (de)serialize into an existing instance (great for Angular) or generate new instances.
  • etc.
  • 整个对象树的序列化和反序列化。
  • 同一对象上的持久性和瞬态属性。
  • 用于自定义(反)序列化逻辑的钩子。
  • 它可以(反)序列化到现有实例(非常适合 Angular)或生成新实例。
  • 等等。

Example:

例子:

class Tree {
  @deserialize public species : string; 
  @deserializeAs(Leaf) public leafs : Array<Leaf>;  //arrays do not need extra specifications, just a type.
  @deserializeAs(Bark, 'barkType') public bark : Bark;  //using custom type and custom key name
  @deserializeIndexable(Leaf) public leafMap : {[idx : string] : Leaf}; //use an object as a map
}

class Leaf {
  @deserialize public color : string;
  @deserialize public blooming : boolean;
  @deserializeAs(Date) public bloomedAt : Date;
}

class Bark {
  @deserialize roughness : number;
}

var json = {
  species: 'Oak',
  barkType: { roughness: 1 },
  leafs: [ {color: 'red', blooming: false, bloomedAt: 'Mon Dec 07 2015 11:48:20 GMT-0500 (EST)' } ],
  leafMap: { type1: { some leaf data }, type2: { some leaf data } }
}
var tree: Tree = Deserialize(json, Tree);

回答by David Siegel

I've created a tool that generates TypeScript interfaces and a runtime "type map" for performing runtime typechecking against the results of JSON.parse: ts.quicktype.io

我创建了一个生成 TypeScript 接口的工具和一个运行时“类型映射”,用于根据以下结果执行运行时类型检查JSON.parsets.quicktype.io

For example, given this JSON:

例如,给定这个 JSON:

{
  "name": "David",
  "pets": [
    {
      "name": "Smoochie",
      "species": "rhino"
    }
  ]
}

quicktypeproduces the following TypeScript interface and type map:

quicktype生成以下 TypeScript 接口和类型映射:

export interface Person {
    name: string;
    pets: Pet[];
}

export interface Pet {
    name:    string;
    species: string;
}

const typeMap: any = {
    Person: {
        name: "string",
        pets: array(object("Pet")),
    },
    Pet: {
        name: "string",
        species: "string",
    },
};

Then we check the result of JSON.parseagainst the type map:

然后我们JSON.parse根据类型映射检查结果:

export function fromJson(json: string): Person {
    return cast(JSON.parse(json), object("Person"));
}

I've left out some code, but you can try quicktypefor the details.

我省略了一些代码,但您可以尝试使用quicktype了解详细信息。

回答by Anthony Brenelière

Option #5: Using Typescript constructors and jQuery.extend

选项 #5:使用 Typescript 构造函数和 jQuery.extend

This seems to be the most maintainable method: add a constructor that takes as parameter the json structure, and extend the json object. That way you can parse a json structure into the whole application model.

这似乎是最易于维护的方法:添加一个以json结构为参数的构造函数,并扩展json对象。这样您就可以将 json 结构解析为整个应用程序模型。

There is no need to create interfaces, or listing properties in constructor.

无需在构造函数中创建接口或列出属性。

export class Company
{
    Employees : Employee[];

    constructor( jsonData: any )
    {
        jQuery.extend( this, jsonData);

        // apply the same principle to linked objects:
        if ( jsonData.Employees )
            this.Employees = jQuery.map( jsonData.Employees , (emp) => {
                return new Employee ( emp );  });
    }

    calculateSalaries() : void { .... }
}

export class Employee
{
    name: string;
    salary: number;
    city: string;

    constructor( jsonData: any )
    {
        jQuery.extend( this, jsonData);

        // case where your object's property does not match the json's:
        this.city = jsonData.town;
    }
}

In your ajax callback where you receive a company to calculate salaries:

在您收到公司来计算工资的 ajax 回调中:

onReceiveCompany( jsonCompany : any ) 
{
   let newCompany = new Company( jsonCompany );

   // call the methods on your newCompany object ...
   newCompany.calculateSalaries()
}

回答by stevex

For simple objects, I like this method:

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

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'});

Leveraging the ability to define properties in the constructor lets it be concise.

利用在构造函数中定义属性的能力让它变得简洁。

This gets you a typed object (vs all the answers that use Object.assign or some variant, which give you an Object) and doesn't require external libraries or decorators.

这为您提供了一个类型化的对象(与使用 Object.assign 或某些变体的所有答案相比,这些答案为您提供了一个对象)并且不需要外部库或装饰器。

回答by Daniel

JQuery .extend does this for you:

JQuery .extend 为您执行此操作:

var mytsobject = new mytsobject();

var newObj = {a:1,b:2};

$.extend(mytsobject, newObj); //mytsobject will now contain a & b

回答by Xavier Méhaut

The 4th option described above is a simple and nice way to do it, which has to be combined with the 2nd option in the case where you have to handle a class hierarchy like for instance a member list which is any of a occurences of subclasses of a Member super class, eg Director extends Member or Student extends Member. In that case you have to give the subclass type in the json format

上面描述的第四个选项是一种简单而好的方法,在必须处理类层次结构的情况下,必须与第二个选项结合使用,例如成员列表,该成员列表是Member 超类,例如 Director extends Member 或 Student extends Member。在这种情况下,您必须以 json 格式提供子类类型

回答by Михайло Пилип

Maybe not actual, but simple solution:

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

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

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

work for difficult dependencies too!!!

也适用于困难的依赖项!!!