我从对REST服务器的AJAX调用中接收到一个JSON对象。这个对象的属性名与我的TypeScript类相匹配(这是这个问题的后续)。
初始化它的最佳方法是什么?我不认为这将工作,因为类(& JSON对象)的成员是对象的列表和成员是类,而这些类的成员是列表和/或类。
但我更喜欢一种方法,查找成员名和分配他们,创建列表和实例化类的需要,所以我不必为每个类中的每个成员写显式代码(有很多!)
我从对REST服务器的AJAX调用中接收到一个JSON对象。这个对象的属性名与我的TypeScript类相匹配(这是这个问题的后续)。
初始化它的最佳方法是什么?我不认为这将工作,因为类(& JSON对象)的成员是对象的列表和成员是类,而这些类的成员是列表和/或类。
但我更喜欢一种方法,查找成员名和分配他们,创建列表和实例化类的需要,所以我不必为每个类中的每个成员写显式代码(有很多!)
当前回答
我已经创建了一个生成TypeScript接口和运行时“类型映射”的工具,用于对JSON的结果执行运行时类型检查。解析:ts.quicktype.io
例如,给定这个JSON:
{
"name": "David",
"pets": [
{
"name": "Smoochie",
"species": "rhino"
}
]
}
quicktype生成以下TypeScript接口和类型映射:
export interface Person {
name: string;
pets: Pet[];
}
export interface Pet {
name: string;
species: string;
}
const typeMap: any = {
Person: {
name: "string",
pets: array(object("Pet")),
},
Pet: {
name: "string",
species: "string",
},
};
然后检查JSON的结果。根据类型映射解析:
export function fromJson(json: string): Person {
return cast(JSON.parse(json), object("Person"));
}
我省略了一些代码,但您可以尝试快速输入细节。
其他回答
我的方法略有不同。我没有将属性复制到新的实例中,我只是改变了现有pojo的原型(在旧的浏览器上可能不太好用)。每个类负责提供一个setprototype方法来设置任何子对象的原型,这些子对象反过来提供它们自己的setprototype方法。
(我也使用_Type属性来获取未知对象的类名,但在这里可以忽略)
class ParentClass
{
public ID?: Guid;
public Child?: ChildClass;
public ListOfChildren?: ChildClass[];
/**
* Set the prototypes of all objects in the graph.
* Used for recursive prototype assignment on a graph via ObjectUtils.SetPrototypeOf.
* @param pojo Plain object received from API/JSON to be given the class prototype.
*/
private static SetPrototypes(pojo: ParentClass): void
{
ObjectUtils.SetPrototypeOf(pojo.Child, ChildClass);
ObjectUtils.SetPrototypeOfAll(pojo.ListOfChildren, ChildClass);
}
}
class ChildClass
{
public ID?: Guid;
public GrandChild?: GrandChildClass;
/**
* Set the prototypes of all objects in the graph.
* Used for recursive prototype assignment on a graph via ObjectUtils.SetPrototypeOf.
* @param pojo Plain object received from API/JSON to be given the class prototype.
*/
private static SetPrototypes(pojo: ChildClass): void
{
ObjectUtils.SetPrototypeOf(pojo.GrandChild, GrandChildClass);
}
}
下面是ObjectUtils.ts:
/**
* ClassType lets us specify arguments as class variables.
* (where ClassType == window[ClassName])
*/
type ClassType = { new(...args: any[]): any; };
/**
* The name of a class as opposed to the class itself.
* (where ClassType == window[ClassName])
*/
type ClassName = string & {};
abstract class ObjectUtils
{
/**
* Set the prototype of an object to the specified class.
*
* Does nothing if source or type are null.
* Throws an exception if type is not a known class type.
*
* If type has the SetPrototypes method then that is called on the source
* to perform recursive prototype assignment on an object graph.
*
* SetPrototypes is declared private on types because it should only be called
* by this method. It does not (and must not) set the prototype of the object
* itself - only the protoypes of child properties, otherwise it would cause a
* loop. Thus a public method would be misleading and not useful on its own.
*
* https://stackoverflow.com/questions/9959727/proto-vs-prototype-in-javascript
*/
public static SetPrototypeOf(source: any, type: ClassType | ClassName): any
{
let classType = (typeof type === "string") ? window[type] : type;
if (!source || !classType)
{
return source;
}
// Guard/contract utility
ExGuard.IsValid(classType.prototype, "type", <any>type);
if ((<any>Object).setPrototypeOf)
{
(<any>Object).setPrototypeOf(source, classType.prototype);
}
else if (source.__proto__)
{
source.__proto__ = classType.prototype.__proto__;
}
if (typeof classType["SetPrototypes"] === "function")
{
classType["SetPrototypes"](source);
}
return source;
}
/**
* Set the prototype of a list of objects to the specified class.
*
* Throws an exception if type is not a known class type.
*/
public static SetPrototypeOfAll(source: any[], type: ClassType): void
{
if (!source)
{
return;
}
for (var i = 0; i < source.length; i++)
{
this.SetPrototypeOf(source[i], type);
}
}
}
用法:
let pojo = SomePlainOldJavascriptObjectReceivedViaAjax;
let parentObject = ObjectUtils.SetPrototypeOf(pojo, ParentClass);
// parentObject is now a proper ParentClass instance
我一直在用这个家伙来做这项工作:https://github.com/weichx/cerialize
它非常简单,但功能强大。它支持:
整个对象树的序列化和反序列化。 同一对象上的持久和瞬态属性。 用于自定义(反)序列化逻辑的钩子。 它可以(反)序列化到一个现有的实例中(这对Angular来说很棒),也可以生成新的实例。 等。
例子:
class Tree {
@deserialize public species : string;
@deserializeAs(Leaf) public leafs : Array<Leaf>; //arrays do not need extra specifications, just a type.
@deserializeAs(Bark, 'barkType') public bark : Bark; //using custom type and custom key name
@deserializeIndexable(Leaf) public leafMap : {[idx : string] : Leaf}; //use an object as a map
}
class Leaf {
@deserialize public color : string;
@deserialize public blooming : boolean;
@deserializeAs(Date) public bloomedAt : Date;
}
class Bark {
@deserialize roughness : number;
}
var json = {
species: 'Oak',
barkType: { roughness: 1 },
leafs: [ {color: 'red', blooming: false, bloomedAt: 'Mon Dec 07 2015 11:48:20 GMT-0500 (EST)' } ],
leafMap: { type1: { some leaf data }, type2: { some leaf data } }
}
var tree: Tree = Deserialize(json, Tree);
另一种选择是使用工厂
export class A {
id: number;
date: Date;
bId: number;
readonly b: B;
}
export class B {
id: number;
}
export class AFactory {
constructor(
private readonly createB: BFactory
) { }
create(data: any): A {
const createB = this.createB.create;
return Object.assign(new A(),
data,
{
get b(): B {
return createB({ id: data.bId });
},
date: new Date(data.date)
});
}
}
export class BFactory {
create(data: any): B {
return Object.assign(new B(), data);
}
}
https://github.com/MrAntix/ts-deserialize
像这样使用
import { A, B, AFactory, BFactory } from "./deserialize";
// create a factory, simplified by DI
const aFactory = new AFactory(new BFactory());
// get an anon js object like you'd get from the http call
const data = { bId: 1, date: '2017-1-1' };
// create a real model from the anon js object
const a = aFactory.create(data);
// confirm instances e.g. dates are Dates
console.log('a.date is instanceof Date', a.date instanceof Date);
console.log('a.b is instanceof B', a.b instanceof B);
保持类简单 工厂可灵活注射
这是我的方法(非常简单):
const jsonObj: { [key: string]: any } = JSON.parse(jsonStr);
for (const key in jsonObj) {
if (!jsonObj.hasOwnProperty(key)) {
continue;
}
console.log(key); // Key
console.log(jsonObj[key]); // Value
// Your logic...
}
也许不现实,但简单的解决方案:
interface Bar{
x:number;
y?:string;
}
var baz:Bar = JSON.parse(jsonString);
alert(baz.y);
对困难的依赖也要努力!!