如何将JSON对象转换为Typescript类

How do I cast a JSON object to a typescript class

我从远程REST服务器读取了JSON对象。 此JSON对象具有Typescript类的所有属性(通过设计)。 如何将收到的JSON对象转换为var类型?

我不想填充一个打字稿变量(即有一个采用此JSON对象的构造函数)。 它很大,因此要在子对象之间按子对象复制所有内容,并按属性复制所有内容将花费大量时间。

更新:但是,您可以将其转换为打字稿界面!


您不能简单地将Ajax请求中的原始JavaScript结果转换为原型JavaScript / TypeScript类实例。有许多技术可以做到这一点,并且通常涉及复制数据。除非您创建该类的实例,否则它将没有任何方法或属性。它将仍然是一个简单的JavaScript对象。

如果仅处理数据,则可以强制转换为接口(因为它纯粹是编译时结构),但这将要求您使用TypeScript类,该类使用数据实例并对该数据执行操作。

复制数据的一些示例:

  • 将AJAX JSON对象复制到现有对象
  • 将JSON字符串解析为JavaScript中的特定对象原型
  • 本质上,您只是:

    1
    2
    var d = new MyRichObject();
    d.copyInto(jsonResult);


    我遇到了同样的问题,我找到了一个可以完成此工作的库:https://github.com/pleerock/class-transformer。

    它是这样的:

    1
    2
    3
            let jsonObject = response.json() as Object;
            let fooInstance = plainToClass(Models.Foo, jsonObject);
            return fooInstance;

    它支持嵌套的子级,但是您必须装饰类的成员。


    在TypeScript中,您可以使用接口和泛型来进行类型断言,如下所示:

    1
    2
    var json = Utilities.JSONLoader.loadFromFile("../docs/location_map.json");
    var locations: Array<ILocationMap> = JSON.parse(json).location;

    ILocationMap在哪里描述数据的形状。此方法的优点是您的JSON可以包含更多属性,但形状可以满足接口的条件。

    希望对您有所帮助!


    如果您使用的是ES6,请尝试以下操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Client{
      name: string

      displayName(){
        console.log(this.name)
      }
    }

    service.getClientFromAPI().then(clientData => {

      // Here the client data from API only have the"name" field
      // If we want to use the Client class methods on this data object we need to:
      let clientWithType = Object.assign(new Client(), clientData)

      clientWithType.displayName()
    })

    但是,遗憾的是,这种方式对嵌套对象无效。


    我发现了一篇关于将JSON通用转换为Typescript类的非常有趣的文章:

    http://cloudmark.github.io/Json-Mapping/

    您最终得到以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    let example = {
                   "name":"Mark",
                   "surname":"Galea",
                   "age": 30,
                   "address": {
                     "first-line":"Some where",
                     "second-line":"Over Here",
                     "city":"In This City"
                    }
                  };

    MapUtils.deserialize(Person, example);  // custom class

    TLDR:一支班轮

    1
    2
    // This assumes your constructor method will assign properties from the arg.
    .map((instanceData: MyClass) => new MyClass(instanceData));

    详细答案

    我不建议使用Object.assign方法,因为它可能会在类实例中使用未在类本身中声明的无关属性(以及定义的闭包)不适当地乱扔垃圾。

    在您要反序列化的类中,我将确保定义了要反序列化的所有属性(null,空数组等)。通过使用初始值定义属性,可以在尝试迭代类成员以为其分配值时公开其可见性(请参见下面的反序列化方法)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    export class Person {
      public name: string = null;
      public favoriteSites: string[] = [];

      private age: number = null;
      private id: number = null;
      private active: boolean;

      constructor(instanceData?: Person) {
        if (instanceData) {
          this.deserialize(instanceData);
        }
      }

      private deserialize(instanceData: Person) {
        // Note this.active will not be listed in keys since it's declared, but not defined
        const keys = Object.keys(this);

        for (const key of keys) {
          if (instanceData.hasOwnProperty(key)) {
            this[key] = instanceData[key];
          }
        }
      }
    }

    在上面的示例中,我只是创建了一个反序列化方法。在真实的示例中,我将其集中在可重用的基类或服务方法中。

    这是在http响应之类的方法中利用此方法的方法...

    1
    2
    3
    this.http.get(ENDPOINT_URL)
      .map(res => res.json())
      .map((resp: Person) => new Person(resp) ) );

    如果tslint / ide抱怨参数类型不兼容,只需使用尖括号将参数转换为相同类型,例如:

    1
    const person = new Person(<Person> { name: 'John', age: 35, id: 1 });

    如果您具有特定类型的类成员(又名:另一个类的实例),则可以通过getter / setter方法将它们转换为类型化的实例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    export class Person {
      private _acct: UserAcct = null;
      private _tasks: Task[] = [];

      // ctor & deserialize methods...

      public get acct(): UserAcct {
        return this.acct;
      }
      public set acct(acctData: UserAcct) {
        this._acct = new UserAcct(acctData);
      }

      public get tasks(): Task[] {
        return this._tasks;
      }

      public set tasks(taskData: Task[]) {
        this._tasks = taskData.map(task => new Task(task));
      }
    }

    上面的示例将acct和任务列表反序列化为它们各自的类实例。


    假设json与您的打字稿类具有相同的属性,则不必将Json属性复制到您的打字稿对象。您只需要构造在构造函数中传递json数据的Typescript对象即可。

    在您的Ajax回调中,您会得到一家公司:

    1
    2
    3
    4
    5
    6
    onReceiveCompany( jsonCompany : any )
    {
       let newCompany = new Company( jsonCompany );

       // call the methods on your newCompany object ...
    }

    为了使该工作:

    1)在您的Typescript类中添加一个以json数据为参数的构造函数。在该构造函数中,您可以使用jQuery扩展json对象,如下所示:$.extend( this, jsonData)。 $ .extend允许在添加json对象的属性时保留javascript原型。

    2)请注意,对于链接对象,您必须执行相同的操作。对于示例中的Employees,您还创建了一个构造函数,该构造函数采用了employees的部分json数据。您调用$ .map将json雇员转换为打字稿Employee对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    export class Company
    {
        Employees : Employee[];

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

            if ( jsonData.Employees )
                this.Employees = $.map( jsonData.Employees , (emp) => {
                    return new Employee ( emp );  });
        }
    }

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

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

    这是我在处理Typescript类和json对象时发现的最佳解决方案。


    就我而言,它有效。我用过的功能
    Object.assign(目标,源...)。
    首先,创建正确的对象,然后将数据从json对象复制到目标。

    1
    2
    let u:User = new User();
    Object.assign(u , jsonUsers);

    以及更高级的使用示例。使用数组的示例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    this.someService.getUsers().then((users: User[]) => {
      this.users = [];
      for (let i in users) {
        let u:User = new User();
        Object.assign(u , users[i]);
        this.users[i] = u;
        console.log("user:" + this.users[i].id);
        console.log("user id from function(test it work) :" + this.users[i].getId());
      }

    });

    export class User {
      id:number;
      name:string;
      fullname:string;
      email:string;

      public getId(){
        return this.id;
      }
    }


    没有什么可以自动检查您从服务器收到的JSON对象是否具有预期的(读取的符合)打字稿的接口属性。但是您可以使用用户定义的类型防护

    考虑以下接口和一个愚蠢的json对象(它可以是任何类型):

    1
    2
    3
    4
    5
    interface MyInterface {
        key: string;
     }

    const json: object = {"key":"value" }

    三种可能的方式:

    A.在变量后放置类型断言或简单的静态强制转换

    1
    const myObject: MyInterface = json as MyInterface;

    B.在变量之前和钻石之间进行简单的静态投射

    1
    const myObject: MyInterface = <MyInterface>json;

    C.高级动态转换,您可以检查自己的对象结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function isMyInterface(json: any): json is MyInterface {
        // silly condition to consider json as conform for MyInterface
        return typeof json.key ==="string";
    }

    if (isMyInterface(json)) {
        console.log(json.key)
    }
    else {
            throw new Error(`Expected MyInterface, got '${json}'.`);
    }

    你可以在这里玩这个例子

    请注意,这里的困难是编写isMyInterface函数。我希望TS早晚添加装饰器,以将复杂的类型导出到运行时,并让运行时在需要时检查对象的结构。现在,您可以使用目的大致相同的json模式验证器,也可以使用此运行时类型检查函数生成器


    虽然它本身不是演员;我发现https://github.com/JohnWhiteTB/TypedJSON是一个有用的选择。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @JsonObject
    class Person {
        @JsonMember
        firstName: string;

        @JsonMember
        lastName: string;

        public getFullname() {
            return this.firstName +"" + this.lastName;
        }
    }
    var person = TypedJSON.parse('{"firstName":"John","lastName":"Doe" }', Person);

    person instanceof Person; // true
    person.getFullname(); //"John Doe"


    您可以创建类型(SomeType)的interface并在其中投射对象。

    1
    const typedObject: SomeType = <SomeType> responseObject;


    一个老问题,大多数情况下是正确的,但不是很有效。我提出以下建议:

    创建一个包含init()方法和静态强制转换方法(用于单个对象和数组)的基类。静态方法可以在任何地方。带有基类和init()的版本之后可以轻松扩展。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    export class ContentItem {
        // parameters: doc - plain JS object, proto - class we want to cast to (subclass of ContentItem)
        static castAs<T extends ContentItem>(doc: T, proto: typeof ContentItem): T {
            // if we already have the correct class skip the cast
            if (doc instanceof proto) { return doc; }
            // create a new object (create), and copy over all properties (assign)
            const d: T = Object.create(proto.prototype);
            Object.assign(d, doc);
            // reason to extend the base class - we want to be able to call init() after cast
            d.init();
            return d;
        }
        // another method casts an array
        static castAllAs<T extends ContentItem>(docs: T[], proto: typeof ContentItem): T[] {
            return docs.map(d => ContentItem.castAs(d, proto));
        }
        init() { }
    }

    @ Adam111p帖子中提到了类似的机制(带有assign())。只是另一种(更完整的)方法。 @Timothy Perez批评了assign(),但是恕我直言,这在这里是完全合适的。

    实现派生的(真实的)类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import { ContentItem } from './content-item';

    export class SubjectArea extends ContentItem {
        id: number;
        title: string;
        areas: SubjectArea[]; // contains embedded objects
        depth: number;

        // method will be unavailable unless we use cast
        lead(): string {
            return '. '.repeat(this.depth);
        }

        // in case we have embedded objects, call cast on them here
        init() {
            if (this.areas) {
                this.areas = ContentItem.castAllAs(this.areas, SubjectArea);
            }
        }
    }

    现在,我们可以转换从服务检索的对象:

    1
    const area = ContentItem.castAs<SubjectArea>(docFromREST, SubjectArea);

    SubjectArea对象的所有层次结构都将具有正确的类。

    用例/示例;创建一个Angular服务(再次抽象基类):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    export abstract class BaseService<T extends ContentItem> {
      BASE_URL = 'http://host:port/';
      protected abstract http: Http;
      abstract path: string;
      abstract subClass: typeof ContentItem;

      cast(source: T): T {
        return ContentItem.castAs(source, this.subClass);
      }
      castAll(source: T[]): T[] {
        return ContentItem.castAllAs(source, this.subClass);
      }

      constructor() { }

      get(): Promise<T[]> {
        const value = this.http.get(`${this.BASE_URL}${this.path}`)
          .toPromise()
          .then(response => {
            const items: T[] = this.castAll(response.json());
            return items;
          });
        return value;
      }
    }

    用法变得非常简单;创建区域服务:

    1
    2
    3
    4
    5
    6
    7
    @Injectable()
    export class SubjectAreaService extends BaseService<SubjectArea> {
      path = 'area';
      subClass = SubjectArea;

      constructor(protected http: Http) { super(); }
    }

    服务的get()方法将返回已转换为SubjectArea对象(整个层次结构)的数组的Promise

    现在说,我们还有另一个课程:

    1
    export class OtherItem extends ContentItem {...}

    创建检索数据并将其强制转换为正确类的服务非常简单:

    1
    2
    3
    4
    5
    6
    7
    @Injectable()
    export class OtherItemService extends BaseService<OtherItem> {
      path = 'other';
      subClass = OtherItem;

      constructor(protected http: Http) { super(); }
    }

    我在这里使用了这个库:https://github.com/pleerock/class-transformer

    1
    2
    <script lang="ts">
        import { plainToClass } from 'class-transformer';

    执行:

    1
    2
    3
    private async getClassTypeValue() {
      const value = await plainToClass(ProductNewsItem, JSON.parse(response.data));
    }

    有时,您必须解析plainToClass的JSON值以了解它是JSON格式的数据


    在后期的TS中,您可以这样做:

    1
    2
    3
    4
    5
    const isMyInterface = (val: any): val is MyInterface => {
      if (!val) { return false; }
      if (!val.myProp) { return false; }
      return true;
    };

    并且比这样的用户:

    1
    2
    3
    if (isMyInterface(data)) {
     // now data will be type of MyInterface
    }

    我遇到了类似的需求。
    我想要一些可以使我轻松地从JSON转换到JSON的东西
    来自对特定类定义的REST api调用。
    我发现的解决方案不足或打算重写我的解决方案
    类的代码,并添加注释或类似内容。

    我想在Java中使用GSON之类的东西来对类从JSON对象进行序列化/反序列化。

    结合以后的需求,即该转换器也将在JS中运行,我结束了编写自己的程序包。

    但是,它有一些开销。但是启动时,添加和编辑非常方便。

    您使用以下命令初始化模块:

  • 转换模式-允许在字段之间进行映射并确定
    转换将如何完成
  • 类映射数组
  • 转换功能映射-用于特殊转换。
  • 然后在您的代码中,使用初始化的模块,例如:

    1
    2
    3
    const convertedNewClassesArray : MyClass[] = this.converter.convert<MyClass>(jsonObjArray, 'MyClass');

    const convertedNewClass : MyClass = this.converter.convertOneObject<MyClass>(jsonObj, 'MyClass');

    或,以JSON表示:

    1
    const jsonObject = this.converter.convertToJson(myClassInstance);

    使用此链接到npm包,以及有关如何使用该模块的详细说明:json-class-converter

    还包裹了
    在以下角度使用:
    角JSON级转换器


    我在前端使用Angular 6,在后端使用Spring Boot应用程序,该应用程序返回Java对象。我需要做的就是在Angular应用程序上定义一个具有匹配属性的类似类,然后我可以将对象"作为"接受一个Angular类对象(在下面的示例中与Company相比较)。

    例如,请参阅下面的前端代码。如果有什么需要进一步说明的,请在评论中让我知道。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
      createCompany() {
        let company = new Company();
        company.name = this.companyName;

        this.companyService.createCompany(company).subscribe(comp => {
           if (comp !== null) {
            this.employerAdminCompany = comp as Company;
          }
        });
      }

    其中company是spring boot app中的实体对象,也是Angular中的类。

    1
    2
    3
    4
    5
    6
    export class Company {
        public id: number;
        public name: string;
        public owner: User;
        public locations: Array<Location>;
    }


    将该对象原样传递给类构造函数;没有约定或检查

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    interface iPerson {
       name: string;
       age: number;
    }

    class Person {
       constructor(private person: iPerson) { }

       toString(): string {
          return this.person.name + ' is ' + this.person.age;
       }  
    }


    // runs this as //
    const object1 = { name: 'Watson1', age: 64 };
    const object2 = { name: 'Watson2' };            // age is missing

    const person1 = new Person(object1);
    const person2 = new Person(object2 as iPerson); // now matches constructor

    console.log(person1.toString())  // Watson1 is 64
    console.log(person2.toString())  // Watson2 is undefined

    这是一个简单且非常好的选择

    1
    2
    3
    4
    let person ="{"name":"Sam","Age":"30"}";

    const jsonParse: ((key: string, value: any) => any) | undefined = undefined;
    let objectConverted = JSON.parse(textValue, jsonParse);

    然后你会

    1
    objectConverted.name