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

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

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


当前回答

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

其他回答

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

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

或者我更喜欢这个

class Person {
  constructor(init?: Partial<Person>){
      Object.assign(this, init);
  }
  name?: string;
  age?: number;
}

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

这是我的方法(非常简单):

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

这是一些快速的镜头,展示了几种不同的方法。他们绝不是“完整的”,作为一个免责声明,我不认为这样做是一个好主意。此外,代码不是太干净,因为我只是键入它在一起相当快。

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

选项#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年3月1日更新:正如@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);

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

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

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