如何将JSON对象转换为Typescript类


393

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

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

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


您可以使用github.com/vojtechhabarta/typescript-generator生成TypeScript接口,以防使用Java类映射您的JSON
Vojta,2015年


1
JSON对象= JavaScript对象符号对象。那里没有足够的对象,我想说我们再增加一些就可以了。
bug-a-lot很多


创建原型TypeScript类来定义您的对象不会损害实际的生产代码。看一下已编译的JS文件,所有定义都将被删除,因为它们不是JS的一部分。
FisNaN

Answers:


167

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

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

复制数据的一些示例:

  1. 将AJAX JSON对象复制到现有对象
  2. 将JSON字符串解析为JavaScript中的特定对象原型

本质上,您只是:

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

我同意你的回答。另外,尽管我现在不能查找和测试它,但是我认为可以通过将唤醒功能作为参数来合并这两个步骤JSON.parse()。两者仍然需要完成,但是从语法上讲,可以将它们组合在一起。
JAAulde 2014年

当然,这也可能有用-我不知道它是否会更有效,因为它需要为每个属性调用额外的函数调用。
WiredPrairie 2014年

?绝对不是我一直在寻找:(出于好奇答案这是为什么在我看来,JavaScript的方式工作,这应该是可行的。
大卫·蒂伦

不,它在TypeScript中不起作用,因为JavaScript中没有简单的方法可以做到这一点。
WiredPrairie 2014年

1
怎么样Object.setPrototypeOf
Petah

102

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

它是这样的:

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

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


14
这个精巧的小图书馆用最少的努力完美解决了问题(不过,请不要忘记您的@Type注释)。这个答案值得更多的赞赏。
Benny Bottema '17

哦,哇!这个库不是那么小,它可能拥有您需要的所有内容,甚至可以让您使用@transform装饰器来控制转换:D
Diego Fernando Murillo Valenci

3
请注意,该库几乎不再维护。它不再与Angular5 +一起使用,并且由于它们甚至不再合并拉取请求,因此我认为他们不会在不久的将来进行处理。这是一个很棒的图书馆。
肯特

Angular5 +有一个解决方法(实际上是一个Angular Bug):github.com/typestack/class-transformer/issues/108
Pak

2
这在Angular 6中效果很好(至少对于我的用例来说,它只是字面上映射JSON <=>类)
tftd18年

54

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

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

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

希望对您有所帮助!


49
仅供参考:这是类型断言,而不是强制类型转换。
WiredPrairie 2015年

6
有关类型断言强制类型转换之间的区别,请参见此处
Stefan Hanke

7
在哪里可以找到Utilities.JSONLoader?
HypeXR '16

22
但是它没有任何方法,如答案中所述。
马丁科尔

1
@StefanHanke看起来URL略有变化:“类型声明与转换”
ruffin

37

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

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

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


4
他们在打字稿中要求它。
joe.feser

嗨@ joe.feser,我提到了ES6,因为这样需要'Object.assign'方法。
migcoder '17

1
如果缺少默认构造函数,则可以通过Object.create(MyClass.prototype)绕过构造函数来创建目标实例。
Marcello

关于限制看到嵌套对象的详细解释stackoverflow.com/questions/22885995/...
迈克尔Freidgeim

28

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

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

您最终得到以下代码:

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

20

TLDR:一支班轮

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

详细答案

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

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

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响应之类的方法中利用此方法的方法...

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

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

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

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

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和任务列表反序列化为它们各自的类实例。


我收到此错误消息:类型'{name:string,age:number,id:number}'无法转换为类型'Person'。属性'id'在类型'Person'中是私有的,但在类型'{name:string,age:number,id:number}'中
不是私有

我应该如何用枚举?我是否必须使用特定的类型方法并为其添加getter和setter?
塔迪亚·巴加里奇(TadijaBagarić)

@TimothyParez何时设置任务?

我尝试做类似的事情,但是当我console.log人时,我的任务数组为空。

为了对此进行编译,我必须向该类添加索引签名:export class Person {[key:string]:any(...)}
Asimov

18

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

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

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对象。

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对象时发现的最佳解决方案。


与实现和维护接口相比,我更喜欢此解决方案,因为我的Angular2应用程序具有真实的应用程序模型,该模型可能与应用程序使用的Web服务模型不同。它可以具有私有数据和计算的属性。
AnthonyBrenelière17年

7
在Angular项目中使用JQuery是一个糟糕的主意。而且,如果您的模型上包含一堆函数,那么它们将不再是模型。
Davor

1
@Davor您的意思是POJO,还是模型?POJO(基本上是普通对象)没有功能,而模型是广义的术语,它包括存储库。与POJO相反,存储库模式是关于函数的,但它仍然是模型。
forsberg

@Davor:在Angular项目中使用JQuery并不是一个坏主意,只要您不使用它来操作DOM,这确实是一个糟糕的主意。我确实使用了我的Angular项目所需的任何库,而对于jQuery,这不是一个选择,因为我的项目使用了依赖于它的SignalR。对于现在由javascript ES6使用的类,将使用属性封装数据的属性来访问数据,该函数封装了将数据存储在内存中的方式。对于构造函数,有一种使用工厂的正确方法。
AnthonyBrenelière17年

OP显然是关于REST的纯数据模型。伙计们,您使它变得不必要地复杂了。是的,您可以使用Jquery进行其他操作,但是您要导入一个庞大的库以使用其中的1%。如果我曾经看过,那是一种代码气味。
Davor

18

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

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

interface MyInterface {
    key: string;
 }

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

三种可能的方式:

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

const myObject: MyInterface = json as MyInterface;

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

const myObject: MyInterface = <MyInterface>json;

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

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模式验证器,也可以使用此运行时类型检查函数生成器


1
你的答案应该是在顶部,我认为
afzalex

绝对是 这是直接依赖ts网站的最佳答案。
BurakKarakuş18

15

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

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

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

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

当您拥有私有财产时会怎样?
prasanthv

因为jsonUser对象不是User类。没有操作Object.assign(u,jsonUsers); 您不能使用getId()函数。只有在分配后,您才能获得可以在其中使用getId()函数的有效User对象。getId()函数仅用于该操作成功的示例。
亚当111p

您可以跳过temp this.users[i] = new User(); Object.assign(this.users[i], users[i])
var-

甚至更好地利用返回值:this.users[i] = Object.assign(new User(), users[i]);
cy18年

此长版仅用于解释。您可以随意缩短代码
长度

6

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

@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"

1
它不是强制转换,实际上是做什么的?
DanielM '17

1
该解决方案需要大量注释。真的没有更简单的方法吗?
JPNotADragon


3

如果您需要将json对象转换为typescript类,并在需要使用的结果对象中使用其实例方法Object.setPrototypeOf,就像我在下面的代码段中所做的那样:

Object.setPrototypeOf(jsonObject, YourTypescriptClass.prototype)

2

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

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

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()很关键,但是恕我直言,这在这里是完全合适的。

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

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

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

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

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

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

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

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

@Injectable()
export class SubjectAreaService extends BaseService<SubjectArea> {
  path = 'area';
  subClass = SubjectArea;

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

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

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

export class OtherItem extends ContentItem {...}

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

@Injectable()
export class OtherItemService extends BaseService<OtherItem> {
  path = 'other';
  subClass = OtherItem;

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

2

使用从接口扩展的类。

然后:

Object.assign(
                new ToWhat(),
                what
              )

最好:

Object.assign(
                    new ToWhat(),
                    <IDataInterface>what
                  )

ToWhat成为DataInterface的控制器。



0

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

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

而且比这样的用户:

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

0

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

我想要在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表示:

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

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

还将其包装为
Angular使用: angular-json-class-converter


0

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

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

0

您可以使用此npm软件包。https://www.npmjs.com/package/class-converter

易于使用,例如:

class UserModel {
  @property('i')
  id: number;

  @property('n')
  name: string;
}

const userRaw = {
  i: 1234,
  n: 'name',
};

// use toClass to convert plain object to class
const userModel = toClass(userRaw, UserModel);
// you will get a class, just like below one
// const userModel = {
//   id: 1234,
//   name: 'name',
// }

0

我个人觉得这很令人震惊,因为打字稿不允许端点定义指定所接收对象的类型。看起来确实是这样,我将用其他语言来做,就是将JSON对象与类定义分开,并且让类定义使用JSON对象作为其唯一的数据成员。 。

我鄙视样板代码,因此对我来说通常是在保留类型的同时以最少的代码量获得所需结果的问题。

考虑以下JSON对象结构定义-这些将是您在端点上收到的内容,它们仅是结构定义,没有方法。

interface IAddress {
    street: string;
    city: string;
    state: string;
    zip: string;
}

interface IPerson {
    name: string;
    address: IAddress;
}

如果我们以面向对象的术语来考虑以上内容,则上述接口不是类,因为它们仅定义了数据结构。OO术语中的类定义数据和对其进行操作的代码。

因此,我们现在定义一个用于指定数据和对其进行操作的代码的类。

class Person {
    person: IPerson;

    constructor(person: IPerson) {
        this.person = person;
    }

    // accessors
    getName(): string {
        return person.name;
    }

    getAddress(): IAddress {
        return person.address;
    }

    // You could write a generic getter for any value in person, 
    // no matter how deep, by accepting a variable number of string params

    // methods
    distanceFrom(address: IAddress): float {
        // Calculate distance from the passed address to this persons IAddress
        return 0.0;
    }
}

现在,我们可以简单地传入任何符合IPerson结构的对象,然后继续前进...

   Person person = new Person({
            name: "persons name",
            address: {
                street: "A street address",
                city: "a city",
                state: "a state",
                zip: "A zipcode"
            }
        });

现在,我们可以用相同的方式处理在您的端点接收到的对象,方法类似于...

Person person = new Person(req.body);    // As in an object received via a POST call

person.distanceFrom({ street: "Some street address", etc.});

这具有更高的性能,并使用了一半的数据复制内存,同时大大减少了您必须为每种实体类型编写的样板代码的数量。它仅依赖于TypeScript提供的类型安全性。



-1

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

let person = "{"name":"Sam","Age":"30"}";

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

然后你会

objectConverted.name


-1

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

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

  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中的类。

export class Company {
    public id: number;
    public name: string;
    public owner: User;
    public locations: Array<Location>;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.