我从远程REST服务器读取了一个JSON对象。这个JSON对象具有TypeScript类的所有属性(出于设计目的)。我该如何将接收到的JSON对象转换为类型变量?
我不想填充TypeScript变量(即具有接受此JSON对象的构造函数)。这很耗时,需要逐个子对象和属性复制。
更新:你可以将其转换为TypeScript接口!请参考此处。
我从远程REST服务器读取了一个JSON对象。这个JSON对象具有TypeScript类的所有属性(出于设计目的)。我该如何将接收到的JSON对象转换为类型变量?
我不想填充TypeScript变量(即具有接受此JSON对象的构造函数)。这很耗时,需要逐个子对象和属性复制。
更新:你可以将其转换为TypeScript接口!请参考此处。
在Ajax请求中,你不能简单地将一个普通的JavaScript结果转换成一个原型JavaScript/TypeScript类实例。有许多技术可以做到这一点,通常涉及复制数据。除非你创建了该类的实例,否则它将保持为一个简单的JavaScript对象,没有任何方法或属性。
如果您只处理数据,则可以将其强制转换为接口(因为它纯粹是编译时结构),但这需要使用一个 TypeScript 类,该类使用数据实例并对该数据执行操作。
以下是一些复制数据的示例:
本质上,你只需要 :
var d = new MyRichObject();
d.copyInto(jsonResult);
Object.setPrototypeOf
怎么样? - PetahObject.assign(new ApiAuthData(), JSON.parse(rawData))
。 - Sam我曾经遇到同样的问题,后来找到了一个能够解决这个问题的库:https://github.com/pleerock/class-transformer。
它的使用方法如下:
let jsonObject = response.json() as Object;
let fooInstance = plainToClass(Models.Foo, jsonObject);
return fooInstance;
它支持嵌套子元素,但您需要对类的成员进行修饰。
@Type
注解)。这个答案值得更多的赞扬。 - Benny BottemaApp.component.ts
中导入 import "reflect-metadata";
)。 - Quentin Griselvar json = Utilities.JSONLoader.loadFromFile("../docs/location_map.json");
var locations: Array<ILocationMap> = JSON.parse(json).location;
ILocationMap描述了数据的形状。这种方法的优点是,您的JSON可能包含更多的属性,但形状满足接口的条件。
然而,这并不会添加类实例方法。
如果您正在使用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()
})
但是不幸的是,这种方法无法在嵌套对象上起作用。
Object.create(MyClass.prototype)
创建目标实例,完全绕过构造函数。 - Marcello我发现了一篇非常有趣的文章,关于将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
目前还没有自动检查从服务器接收到的JSON对象是否符合TypeScript接口属性预期(即符合)。但是您可以使用用户定义的类型保护。
考虑以下接口和愚蠢的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模式验证器,其目的大致相同,或者使用运行时类型检查函数生成器。简述:一句话
// This assumes your constructor method will assign properties from the arg.
.map((instanceData: MyClass) => new MyClass(instanceData));
详细答案
我不建议使用 Object.assign 方法,因为它可能会在类实例中添加一些不相关的属性(以及未在类本身中声明的封闭函数)。
在你想反序列化的类中,我建议确保任何想要反序列化的属性都被定义(为空值、空数组等)。通过用初始值定义属性,你可以在尝试迭代类成员为其赋值时暴露它们的可见性(请参见下面的 deserialize 方法)。
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 resp之类的东西中使用它的方法...
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和任务列表反序列化为它们各自的类实例。
假设JSON对象的属性与TypeScript类相同,您无需将Json属性复制到TypeScript对象中。您只需要通过构造函数传递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允许保留javascript原型并添加json对象的属性。
2)请注意,您还必须对链接对象执行相同的操作。在示例中的Employees的情况下,您还创建了一个构造函数,该函数使用员工json数据的一部分。您调用$.map将json员工转换为typescript 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对象时,我发现这是最好的解决方案。
在我的案例中,它起作用了。我使用了函数 Object.assign(target,sources ...)。 首先,创建正确的对象,然后将数据从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;
}
}
this.users[i] = new User(); Object.assign(this.users[i], users[i])
。 - cyptusthis.users[i] = Object.assign(new User(), users[i]);
- cyptus虽然它不是直接地进行类型转换,但我发现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"