如何将一个JSON对象解析为TypeScript对象

68
我可以帮您将文本翻译成中文。以下是需要翻译的内容:

我目前正在尝试将接收到的JSON对象转换为具有相同属性的TypeScript类,但我无法使其工作。我做错了什么?

员工类

export class Employee{
    firstname: string;
    lastname: string;
    birthdate: Date;
    maxWorkHours: number;
    department: string;
    permissions: string;
    typeOfEmployee: string;
    note: string;
    lastUpdate: Date;
}

员工字符串
{
    "department": "<anystring>",
    "typeOfEmployee": "<anystring>",
    "firstname": "<anystring>",
    "lastname": "<anystring>",
    "birthdate": "<anydate>",
    "maxWorkHours": <anynumber>,
    "username": "<anystring>",
    "permissions": "<anystring>",
    "lastUpdate": "<anydate>"
    //I will add note later
}

我的尝试
let e: Employee = new Employee();

Object.assign(e, {
    "department": "<anystring>",
    "typeOfEmployee": "<anystring>",
    "firstname": "<anystring>",
    "lastname": "<anystring>",
    "birthdate": "<anydate>",
    "maxWorkHours": 3,
    "username": "<anystring>",
    "permissions": "<anystring>",
    "lastUpdate": "<anydate>"
});

console.log(e);

Typescript Playground链接


具体是什么问题?无法编译吗?如果是这样,出现了什么错误? - goenning
我编辑了我的问题。现在它可以工作了,但是该对象被识别为Object而不是Employee。 - moessi774
请查看此gist并在playground中尝试。 employee变量具有两个可用属性。 - goenning
可能是重复的问题:如何将 JSON 对象转换为 TypeScript 类 - Franklin Yu
12个回答

68

如果你使用一个 TypeScript 接口 而不是一个,事情就变得简单了:

export interface Employee {
    typeOfEmployee_id: number;
    department_id: number;
    permissions_id: number;
    maxWorkHours: number;
    employee_id: number;
    firstname: string;
    lastname: string;
    username: string;
    birthdate: Date;
    lastUpdate: Date;
}

let jsonObj = JSON.parse(employeeString); // string to "any" object first
let employee = jsonObj as Employee;

如果您想要一个,简单的转换将不起作用。例如:

class Foo {
    name: string;
    public pump() { }
}

let jsonObj = JSON.parse('{ "name":"hello" }');
let fObj = jsonObj as Foo;
fObj.pump(); // crash, method is undefined!
为了一个类,您需要编写一个构造函数,该函数接受一个 JSON 字符串/对象,然后通过迭代属性手动分配每个成员,就像这样:
class Foo {
    name: string;

    constructor(jsonStr: string) {
        let jsonObj = JSON.parse(jsonStr);
        for (let prop in jsonObj) {
            this[prop] = jsonObj[prop];
        }
    }
}

let fObj = new Foo(theJsonString);

1
这对我来说似乎很合理。我刚刚看到我的JSON转换器在源代码中发送了id,因为那里的员工类有它们。但它应该发送id所指向的值。我会进行调整,然后尝试像你的示例一样进行转换。 - moessi774
我调整了JSON字符串并在上面更新了我的问题。我还实现了你的解决方案,但它仍然无法识别为Employee并抛出类型不匹配错误。 - moessi774
我现在还不知道接口。所以,如果我只是使用我的类进行类型化,那么使用接口会更加聪明。谢谢你的回答。 - moessi774
这个好的答案来自2016年。使用ES6,您可以在对象中的函数内部使用Object.assign(this, input)来避免手动迭代属性。但是,您仍然需要手动处理对象嵌套。 - Guillermo Prandi
@rodrigocfd 如果我的接口与JSON对象中的字段相比较少,会怎样呢? - Lakshya Sharma
显示剩余3条评论

46
编译器允许你将从JSON.parse返回的对象强制转换为类,是因为typescript基于结构子类型化
你实际上没有一个Employee的实例,你有一个具有相同属性的对象(正如你在控制台中看到的)。
一个更简单的例子:
class A {
    constructor(public str: string, public num: number) {}
}

function logA(a: A) {
    console.log(`A instance with str: "${ a.str }" and num: ${ a.num }`);
}

let a1 = { str: "string", num: 0, boo: true };
let a2 = new A("stirng", 0);
logA(a1); // no errors
logA(a2);

(在 playground 中的代码)

这里没有出现错误,因为 a1 满足类型 A,因为它拥有所有的属性,而且只要具有相同的属性,logA 函数就可以被调用而没有运行时错误。

当你的类是简单的数据对象且没有方法时,这样做非常好用,但是一旦你引入方法,事情往往会出错:

class A {
    constructor(public str: string, public num: number) { }

    multiplyBy(x: number): number {
        return this.num * x;
    }
}

// this won't compile:
let a1 = { str: "string", num: 0, boo: true } as A; // Error: Type '{ str: string; num: number; boo: boolean; }' cannot be converted to type 'A'

// but this will:
let a2 = { str: "string", num: 0 } as A;

// and then you get a runtime error:
a2.multiplyBy(4); // Error: Uncaught TypeError: a2.multiplyBy is not a function

(在 playground 中查看代码)


编辑

这段代码没有问题:

const employeeString = '{"department":"<anystring>","typeOfEmployee":"<anystring>","firstname":"<anystring>","lastname":"<anystring>","birthdate":"<anydate>","maxWorkHours":0,"username":"<anystring>","permissions":"<anystring>","lastUpdate":"<anydate>"}';
let employee1 = JSON.parse(employeeString);
console.log(employee1);

如果您试图在对象不是字符串的情况下使用 JSON.parse

(播放窗口中的代码)

let e = {
    "department": "<anystring>",
    "typeOfEmployee": "<anystring>",
    "firstname": "<anystring>",
    "lastname": "<anystring>",
    "birthdate": "<anydate>",
    "maxWorkHours": 3,
    "username": "<anystring>",
    "permissions": "<anystring>",
    "lastUpdate": "<anydate>"
}
let employee2 = JSON.parse(e);

如果你得到了错误,因为它不是一个字符串,而是一个对象,如果你已经以这种形式拥有它,那么就没有必要使用JSON.parse

但是,正如我所写的,如果你选择这种方式,那么你将不会有类的实例,只有一个具有与类成员相同属性的对象。

如果你想要一个实例,那么:

let e = new Employee();
Object.assign(e, {
    "department": "<anystring>",
    "typeOfEmployee": "<anystring>",
    "firstname": "<anystring>",
    "lastname": "<anystring>",
    "birthdate": "<anydate>",
    "maxWorkHours": 3,
    "username": "<anystring>",
    "permissions": "<anystring>",
    "lastUpdate": "<anydate>"
});

1
请检查我的修订后的答案。 - Nitzan Tomer
如果我按照你的例子使用新实例来执行此操作,最终会得到一个对象,其中每个字符都成为了一个单独的属性。 我已经在我的问题中编辑并附上了错误信息。 - moessi774
1
我无法理解你的意思。请更新你的问题并解释你做了什么以及得到了什么回复。 - Nitzan Tomer
1
使用您的准确代码,我得到了:Employee {department: "<任意字符串>", typeOfEmployee: "<任意字符串>", firstname: "<任意字符串>", lastname: "<任意字符串>", birthdate: "<任意日期>"…} 这是可以的。 - Nitzan Tomer
我完全不明白。你确定你在问题中发布的代码(在你的编辑中)就是你想要运行的代码吗?请前往 TypeScript Playground,在那里重现问题,然后分享该 Playground 到你的问题中。 - Nitzan Tomer
显示剩余12条评论

7
let employee = <Employee>JSON.parse(employeeString);

请记住:强类型是编译时的限制,因为 JavaScript 不支持它。


2
你的例子和他的有什么区别? - goenning

6

您的JSON数据可能具有一些在您的类中不存在的属性。为了进行映射,您可以进行简单的自定义映射。

export class Employe{ ////
    static parse(json: string) {
           var data = JSON.parse(json);
            return new Employe(data.typeOfEmployee_id, data.firstName.. and others);
       }
}

同时在您的Employee类中指定构造函数。


这看起来是一个不错的解决方案。我会用我的当前方法再试几次。如果还是不行,我就采用你的方法。 - moessi774

2
我喜欢使用一个叫做class-transformer的小型库。
它可以处理嵌套对象,将字符串映射为日期对象,并处理不同的JSON属性名称等等。
也许值得一看。
import { Type, plainToClass, Expose } from "class-transformer";
import 'reflect-metadata';

export class Employee{
    @Expose({ name: "uid" })
    id: number;

    firstname: string;
    lastname: string;
    birthdate: Date;
    maxWorkHours: number;
    department: string;

    @Type(() => Permission)
    permissions: Permission[] = [];
    typeOfEmployee: string;
    note: string;

    @Type(() => Date)
    lastUpdate: Date;
}

export class Permission {
  type : string;
}

let json:string = {
    "uid": 123,
    "department": "<anystring>",
    "typeOfEmployee": "<anystring>",
    "firstname": "<anystring>",
    "lastname": "<anystring>",
    "birthdate": "<anydate>",
    "maxWorkHours": 1,
    "username": "<anystring>",
    "permissions": [
      {'type' : 'read'},
      {'type' : 'write'}
    ],
    "lastUpdate": "2020-05-08"
}

console.log(plainToClass(Employee, json));

```


同意。对于具有嵌套对象层次结构的非平凡情况,class-transformer 是一个有用的工具,它极大地减少了应用程序在 JSON 格式中发送/接收外部数据所需的手动反序列化代码量。 - colin moock

1

首先,您需要确保来自服务的所有属性在您的类中具有相同的名称。然后,您可以解析该对象,然后将其分配给新变量,类似于以下内容:

const parsedJSON = JSON.parse(serverResponse);
const employeeObj: Employee = parsedJSON as Employee;

试试看!


此解决方案有一个大问题 - 不支持嵌套对象。 - kris_IV

0

这里有一个简洁的解决方案,适用于简单的“平面”对象:

let listOfObjectsWithMethods = listFromBackend.map( o => Object.assign(new MyType(), o));

一旦您执行此转换,您将能够访问在MyType类中声明的对象的方法。


0

你可以创建一个新的类对象,然后从JSON对象的参数动态地分配它的参数。

const employeeData = JSON.parse(employeeString);
let emp:Employee=new Employee();
const keys=Object.keys(employeeData);
keys.forEach(key=>{
    emp[key]=employeeData[key];
});
console.log(emp);

现在,emp是一个Employee对象,包含了employeeString的Json对象(employeeData)中所有的字段。


0
你可以使用 "as" 语法执行此操作吗?
async getProfile(): Promise<Contact> {
      const url: string = this.baseApi;
    
      const response = await this.http.get(url).toPromise()
      return JSON.parse(response.json()) as Contact;
    }

0

尝试在你的类中使用构造函数过程。

Object.assign

是一个关键

请看这个示例:

class Employee{
    firstname: string;
    lastname: string;
    birthdate: Date;
    maxWorkHours: number;
    department: string;
    permissions: string;
    typeOfEmployee: string;
    note: string;
    lastUpdate: Date;

    constructor(original: Object) { 
        Object.assign(this, original);
    }
}

let e = new Employee({
    "department": "<anystring>",
    "typeOfEmployee": "<anystring>",
    "firstname": "<anystring>",
    "lastname": "<anystring>",
    "birthdate": "<anydate>",
    "maxWorkHours": 3,
    "username": "<anystring>",
    "permissions": "<anystring>",
    "lastUpdate": "<anydate>"
});
console.log(e);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接