我从远程REST服务器读取了一个JSON对象。这个JSON对象具有typescript类的所有属性(根据设计)。我如何转换收到的JSON对象的类型var?
我不想填充一个typescript变量(即有一个构造函数,以这个JSON对象)。它很大,在子对象和属性之间复制所有内容将花费大量时间。
更新:你可以将它转换为typescript接口!
我从远程REST服务器读取了一个JSON对象。这个JSON对象具有typescript类的所有属性(根据设计)。我如何转换收到的JSON对象的类型var?
我不想填充一个typescript变量(即有一个构造函数,以这个JSON对象)。它很大,在子对象和属性之间复制所有内容将花费大量时间。
更新:你可以将它转换为typescript接口!
当前回答
这是一个老问题,答案基本正确,但不是很有效。我的建议是:
创建一个基类,其中包含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() { }
}
类似的机制(使用assign())已经在@Adam111p的帖子中提到过。只是另一种(更完整的)方法。@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);
}
}
}
现在我们可以强制转换从service检索到的对象:
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;
}
}
用法变得非常简单;创建Area服务:
@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(); }
}
其他回答
你可以使用这个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',
// }
TLDR:一个班轮
// This assumes your constructor method will assign properties from the arg.
.map((instanceData: MyClass) => new MyClass(instanceData));
详细回答
我不推荐Object。赋值方法,因为它会不恰当地用类本身中没有声明的不相关属性(以及定义的闭包)丢弃类实例。
在您试图反序列化的类中,我将确保您想要反序列化的任何属性都已定义(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和任务列表反序列化到它们各自的类实例中。
我有同样的问题,我已经找到了一个库,可以做这项工作:https://github.com/pleerock/class-transformer。
它是这样工作的:
let jsonObject = response.json() as Object;
let fooInstance = plainToClass(Models.Foo, jsonObject);
return fooInstance;
它支持嵌套的子类,但是你必须修饰你的类成员。
目前还没有自动检查从服务器接收到的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模式验证器,其目的与此大致相同,也可以使用此运行时类型检查函数生成器
如果你需要将json对象转换为typescript类,并在结果对象中使用它的实例方法,你需要使用object。setPrototypeOf,就像我在下面的代码片段中所做的那样:
Object.setPrototypeOf(jsonObject, YourTypescriptClass.prototype)