【问题标题】:How do I initialize a TypeScript Object with a JSON-Object?如何使用 JSON 对象初始化 TypeScript 对象?
【发布时间】:2014-05-18 03:16:22
【问题描述】:

我从对 REST 服务器的 AJAX 调用接收到一个 JSON 对象。该对象的属性名称与我的 TypeScript 类匹配(这是 this question 的后续版本)。

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

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

【问题讨论】:

  • 你为什么又问这个问题(因为我在另一个问题中提供的答案说这行不通,而且它是关于将属性复制到现有对象中)?
  • @WiredPrairie 这个问题是不同的,它询问我是否可以逐个遍历属性并将它们分配。其他问题是问我是否可以施放它。
  • @WiredPrairie 续:如果您一直深入研究属性,直到只找到原始类型,那么这些可以被分配。
  • 它仍在复制所有值,就像我建议您需要做的那样。在 TypeScript 中没有新的方法可以做到这一点,因为它是 JavaScript 的基本设计。对于大型对象,您可能不想复制任何值而只是“作用于”数据结构。

标签: json typescript


【解决方案1】:

如果你想要类型安全并且不喜欢装饰器

abstract class IPerson{
  name?: string;
  age?: number;
}
class Person extends IPerson{
  constructor({name, age}: IPerson){
    super();
    this.name = name;
    this.age = age;
  }
}

const json = {name: "ali", age: 80};
const person = new Person(json);

【讨论】:

    【解决方案2】:

    这是我的方法(很简单):

    const jsonObj: { [key: string]: any } = JSON.parse(jsonStr);
    
    for (const key in jsonObj) {
      if (!jsonObj.hasOwnProperty(key)) {
        continue;
      }
    
      console.log(key); // Key
      console.log(jsonObj[key]); // Value
      // Your logic...
    }
    

    【讨论】:

      【解决方案3】:

      我的方法略有不同。我不会将属性复制到新实例中,我只是更改现有 POJO 的原型(在旧浏览器上可能无法正常工作)。每个类负责提供一个 SetPrototypes 方法来设置任何子对象的原型,而这些子对象又提供自己的 SetPrototypes 方法。

      (我也使用 _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
      

      【讨论】:

        【解决方案4】:

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

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

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

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

        选项 #2:name 属性

        为了摆脱选项 #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);
        

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

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

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

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

        2016 年 1 月 3 日更新: 正如 @GameAlchemist 在 cmets 中指出的那样(ideaimplementation),从 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);
        

        【讨论】:

        • 选项#4 是我所说的合理方法。您仍然需要编写反序列化代码,但它在同一类中并且完全可控。如果您来自 Java,那么这相当于必须编写 equalstoString 方法(只是您通常会自动生成它们)。如果您愿意,为deserialize 编写一个生成器应该不会难,但它不可能是运行时自动化。
        • @IngoBürk,我知道两年后我会问这个问题,但是这将如何处理对象数组?上面的示例代码适用于 JSON 对象。它如何用于对象数组?
        • 附注:自 1.7 以来(诚然比您的答案更新),typescript 提供了类/属性装饰器,允许以更简洁的方式编写第 4 个解决方案。
        • 我找到的最好的文档是 StackOverflow 的答案:stackoverflow.com/a/29837695/856501。我在我的一个项目中使用了装饰器,虽然我想要其他一些功能,但我不得不说它们就像一个魅力。
        • 我现在还不会在生产项目中使用装饰器 - 请记住,它们仍然是一个实验性功能。我不会将真实世界的代码基于“实验”,因为就我们而言,它们可能会在下一个版本中消失,您必须重写一堆代码,或者永远停留在旧的 TS 版本上。只是我的 $.02
        【解决方案5】:

        我个人更喜欢@Ingo Bürk 的选项#3。 我改进了他的代码以支持复杂数据数组和原始数据数组。

        interface IDeserializable {
          getTypes(): Object;
        }
        
        class Utility {
          static deserializeJson<T>(jsonObj: object, classType: any): T {
            let instanceObj = new classType();
            let types: IDeserializable;
            if (instanceObj && instanceObj.getTypes) {
              types = instanceObj.getTypes();
            }
        
            for (var prop in jsonObj) {
              if (!(prop in instanceObj)) {
                continue;
              }
        
              let jsonProp = jsonObj[prop];
              if (this.isObject(jsonProp)) {
                instanceObj[prop] =
                  types && types[prop]
                    ? this.deserializeJson(jsonProp, types[prop])
                    : jsonProp;
              } else if (this.isArray(jsonProp)) {
                instanceObj[prop] = [];
                for (let index = 0; index < jsonProp.length; index++) {
                  const elem = jsonProp[index];
                  if (this.isObject(elem) && types && types[prop]) {
                    instanceObj[prop].push(this.deserializeJson(elem, types[prop]));
                  } else {
                    instanceObj[prop].push(elem);
                  }
                }
              } else {
                instanceObj[prop] = jsonProp;
              }
            }
        
            return instanceObj;
          }
        
          //#region ### get types ###
          /**
           * check type of value be string
           * @param {*} value
           */
          static isString(value: any) {
            return typeof value === "string" || value instanceof String;
          }
        
          /**
           * check type of value be array
           * @param {*} value
           */
          static isNumber(value: any) {
            return typeof value === "number" && isFinite(value);
          }
        
          /**
           * check type of value be array
           * @param {*} value
           */
          static isArray(value: any) {
            return value && typeof value === "object" && value.constructor === Array;
          }
        
          /**
           * check type of value be object
           * @param {*} value
           */
          static isObject(value: any) {
            return value && typeof value === "object" && value.constructor === Object;
          }
        
          /**
           * check type of value be boolean
           * @param {*} value
           */
          static isBoolean(value: any) {
            return typeof value === "boolean";
          }
          //#endregion
        }
        
        // #region ### Models ###
        class Hotel implements IDeserializable {
          id: number = 0;
          name: string = "";
          address: string = "";
          city: City = new City(); // complex data
          roomTypes: Array<RoomType> = []; // array of complex data
          facilities: Array<string> = []; // array of primitive data
        
          // getter example
          get nameAndAddress() {
            return `${this.name} ${this.address}`;
          }
        
          // function example
          checkRoom() {
            return true;
          }
        
          // this function will be use for getting run-time type information
          getTypes() {
            return {
              city: City,
              roomTypes: RoomType
            };
          }
        }
        
        class RoomType implements IDeserializable {
          id: number = 0;
          name: string = "";
          roomPrices: Array<RoomPrice> = [];
        
          // getter example
          get totalPrice() {
            return this.roomPrices.map(x => x.price).reduce((a, b) => a + b, 0);
          }
        
          getTypes() {
            return {
              roomPrices: RoomPrice
            };
          }
        }
        
        class RoomPrice {
          price: number = 0;
          date: string = "";
        }
        
        class City {
          id: number = 0;
          name: string = "";
        }
        // #endregion
        
        // #region ### test code ###
        var jsonObj = {
          id: 1,
          name: "hotel1",
          address: "address1",
          city: {
            id: 1,
            name: "city1"
          },
          roomTypes: [
            {
              id: 1,
              name: "single",
              roomPrices: [
                {
                  price: 1000,
                  date: "2020-02-20"
                },
                {
                  price: 1500,
                  date: "2020-02-21"
                }
              ]
            },
            {
              id: 2,
              name: "double",
              roomPrices: [
                {
                  price: 2000,
                  date: "2020-02-20"
                },
                {
                  price: 2500,
                  date: "2020-02-21"
                }
              ]
            }
          ],
          facilities: ["facility1", "facility2"]
        };
        
        var hotelInstance = Utility.deserializeJson<Hotel>(jsonObj, Hotel);
        
        console.log(hotelInstance.city.name);
        console.log(hotelInstance.nameAndAddress); // getter
        console.log(hotelInstance.checkRoom()); // function
        console.log(hotelInstance.roomTypes[0].totalPrice); // getter
        // #endregion
        
        

        【讨论】:

          【解决方案6】:

          我创建了一个生成 TypeScript 接口和运行时“类型映射”的工具,用于针对 JSON.parse:ts.quicktype.io 的结果执行运行时类型检查

          例如,给定这个 JSON:

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

          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",
              },
          };
          

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

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

          我遗漏了一些代码,但您可以尝试quicktype 了解详细信息。

          【讨论】:

          • 经过数小时的研究并尝试了几种解析技术,我可以说这是一个很好的解决方案——主要是因为装饰器仍处于试验阶段。 * 原始链接对我来说已损坏;但ts.quicktype.io 有效。 * 将 JSON 转换为 JSON Schema 是很好的第一步。
          【解决方案7】:

          我为此目的找到的最好的是类转换器。 github.com/typestack/class-transformer

          这就是你使用它的方式:

          某类:

          export class Foo {
          
              name: string;
          
              @Type(() => Bar)
              bar: Bar;
          
              public someFunction = (test: string): boolean => {
                  ...
              }
          }
          
          
          import { plainToClass } from 'class-transformer';
          
          export class SomeService {
          
            anyFunction() {
          u = plainToClass(Foo, JSONobj);
           }
          

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

          【讨论】:

            【解决方案8】:

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

            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.assign 或某些变体的所有答案相比,它为您提供一个对象)并且不需要外部库或装饰器。

            【讨论】:

              【解决方案9】:
              **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;
                  }
              }
              

              【讨论】:

              【解决方案10】:

              使用工厂的另一种选择

              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);
              
              1. 让您的课程简单
              2. 为工厂提供注射以提高灵活性

              【讨论】:

                【解决方案11】:

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

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

                • 整个对象树的序列化和反序列化。
                • 同一对象的持久性和瞬态属性。
                • 用于自定义(反)序列化逻辑的挂钩。
                • 它可以(反)序列化为现有实例(非常适合 Angular)或生成新实例。

                例子:

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

                【讨论】:

                  【解决方案12】:

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

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

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

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

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

                  onReceiveCompany( jsonCompany : any ) 
                  {
                     let newCompany = new Company( jsonCompany );
                  
                     // call the methods on your newCompany object ...
                     newCompany.calculateSalaries()
                  }
                  

                  【讨论】:

                  • $.extend 来自哪里?
                  • @whale_steward 我假设作者指的是 jQuery 库。在 JavaScript 世界中,'$' 通常是使用 jQuery 的人。
                  • 如何导入?把它包含在 html 头部就足够了?
                  • 是的,我更新了答案以将 $ 替换为 jQuery。在 html 头中导入 jQuery.js,并在你的 package.json、devDependencies 部分安装并添加 @types/jquery。
                  • 请注意,在 Javascript 中,您应该执行 Object.assign,这将删除对 j​​Query 的依赖。
                  【解决方案13】:

                  你可以用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 );
                  

                  这里是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
                  

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

                  【讨论】:

                  • 所以,基本上,就是这样:Object.assign。那为什么我们在这个上面有两个类似词典的答案呢?
                  • @Blauhim 因为Object.assign 不会递归工作,也不会实例化正确的对象类型,将值保留为Object 实例。虽然它适用于琐碎的任务,但复杂的类型序列化是不可能的。例如,如果一个类属性是自定义类类型,JSON.parse + Object.assign 会将该属性实例化为Object。副作用包括缺少方法和访问器。
                  • @JohnWeisz 对象分配的顶级类确实具有正确的类型,我在其中提到了递归的东西......也就是说,YMMV,那些可能是交易破坏者。
                  • 直接从问题中引用:“该类的成员是对象列表和类成员,而这些类的成员是列表和/或类 [...]我更喜欢一种查找成员名称并分配它们的方法,根据需要创建列表和实例化类,所以我不必为每个类中的每个成员编写显式代码"——Object.assign 不是这种情况,它仍然归结为手动编写嵌套实例化。这种方法适用于非常简单的教程级对象,但不适用于实际使用。
                  • @JohnWeisz 当然,大部分都回答了这个问题,因为它不在任何答案中,并且对于某些用例来说似乎很简单。我敢肯定它也可以与其他答案(例如反射)结合使用,以完成您正在寻找的事情。我也写了一部分,以便我以后记得。查看这些答案并使用并编写了更强大的库,似乎没有任何东西可以“真正使用”。
                  【解决方案14】:

                  TLDR:TypedJSON(工作概念证明)


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

                  幸运的是,这可以通过decoratorsReflectDecorators 以一种非常优雅和强大的方式解决:

                  1. 在需要序列化的属性上使用property decorators,以记录元数据信息并将该信息存储在某处,例如类原型中
                  2. 将此元数据信息提供给递归初始化程序(反序列化程序)

                   

                  记录类型信息

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

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

                  class Language {
                      @JsonMember // String
                      name: string;
                  
                      @JsonMember// Number
                      level: number;
                  }
                  
                  class Person {
                      @JsonMember // String
                      name: string;
                  
                      @JsonMember// Language
                      language: Language;
                  }
                  

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

                   

                  处理类型信息

                  我们首先需要使用JSON.parse 获得一个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);
                  

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

                   

                  边缘案例

                  但是,如果您现在对解决方案那么简单感到高兴,那么我有一些坏消息:需要采取大量数量的边缘情况照顾。只有其中一些是:

                  • 数组和数组元素(尤其是嵌套数组)
                  • 多态性
                  • 抽象类和接口
                  • ...

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

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

                  【讨论】:

                  • TypedJSON 效果很好;非常感谢您的参考。
                  • 干得好,您为困扰我一段时间的问题提出了一个非常优雅的解决方案。我会密切关注您的项目!
                  【解决方案15】:

                  你可以像下面那样做

                  export interface Instance {
                    id?:string;
                    name?:string;
                    type:string;
                  }
                  

                  var instance: Instance = <Instance>({
                        id: null,
                        name: '',
                        type: ''
                      });
                  

                  【讨论】:

                  • 这实际上不会产生预期对象类型的运行时实例。当您的类型仅具有原始属性时,它似乎可以工作,但当类型具有方法时它将失败。接口定义在运行时也不可用(仅在构建时)。
                  【解决方案16】:

                  JQuery .extend 为你做这件事:

                  var mytsobject = new mytsobject();
                  
                  var newObj = {a:1,b:2};
                  
                  $.extend(mytsobject, newObj); //mytsobject will now contain a & b
                  

                  【讨论】:

                    【解决方案17】:

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

                    interface Bar{
                    x:number;
                    y?:string; 
                    }
                    
                    var baz:Bar = JSON.parse(jsonString);
                    alert(baz.y);
                    

                    也为困难的依赖工作!!!

                    【讨论】:

                    • 这种方法实际上并没有按预期工作。如果您检查运行时结果,baz 将是 Object 类型而不是 Bar. 类型。它适用于这种简单的情况,因为 Bar 没有方法(只是原始属性)。如果Bar 有类似isEnabled() 的方法,则此方法将失败,因为该方法不在序列化的JSON 字符串中。
                    【解决方案18】:

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

                    【讨论】:

                      猜你喜欢
                      • 2013-09-02
                      • 2019-03-08
                      • 2013-06-27
                      • 2017-11-03
                      • 1970-01-01
                      • 1970-01-01
                      • 2013-12-06
                      • 1970-01-01
                      相关资源
                      最近更新 更多