Dav*_*len 179 json typescript
我从AJAX调用到REST服务器收到一个JSON对象.此对象具有与我的TypeScript类匹配的属性名称(这是此问题的后续内容).
初始化它的最佳方法是什么?我认为这不会起作用,因为类(&JSON对象)的成员是对象列表和成员类,而这些类的成员是列表和/或类.
但我更喜欢一种方法,它查找成员名称并将它们分配,创建列表并根据需要实例化类,因此我不必为每个类中的每个成员编写显式代码(有很多!)
Ing*_*ürk 181
这些是一些快速拍摄,以显示几种不同的方式.它们绝不是"完整的",作为免责声明,我不认为这样做是个好主意.此外,代码不是太干净,因为我只是很快地将它们拼凑在一起.
另外作为注释:当然,可解串的类需要有默认的构造函数,就像我知道任何类型的反序列化的所有其他语言一样.当然,如果你调用一个没有参数的非默认构造函数,Javascript就不会抱怨,但是这个类更好地为它做好准备(而且,它实际上不是"typescripty方式").
这种方法的问题主要是任何成员的名称必须与其类匹配.这会自动将您限制为每个班级相同类型的一名成员,并违反了一些良好做法规则.我强烈反对这一点,但只是在这里列出,因为这是我写这个答案时的第一个"草稿"(这也是为什么名字是"Foo"等).
module Environment {
export class Sub {
id: number;
}
export class Foo {
baz: number;
Sub: Sub;
}
}
function deserialize(json, environment, clazz) {
var instance = new clazz();
for(var prop in json) {
if(!json.hasOwnProperty(prop)) {
continue;
}
if(typeof json[prop] === 'object') {
instance[prop] = deserialize(json[prop], environment, environment[prop]);
} else {
instance[prop] = json[prop];
}
}
return instance;
}
var json = {
baz: 42,
Sub: {
id: 1337
}
};
var instance = deserialize(json, Environment, Environment.Foo);
console.log(instance);
Run Code Online (Sandbox Code Playgroud)
为了摆脱选项#1中的问题,我们需要获得JSON对象中节点类型的某种信息.问题是在Typescript中,这些东西是编译时构造,我们在运行时需要它们 - 但运行时对象在设置之前根本不知道它们的属性.
一种方法是让类知道他们的名字.但是,您也需要在JSON中使用此属性.实际上,你只需要在json中:
module Environment {
export class Member {
private __name__ = "Member";
id: number;
}
export class ExampleClass {
private __name__ = "ExampleClass";
mainId: number;
firstMember: Member;
secondMember: Member;
}
}
function deserialize(json, environment) {
var instance = new environment[json.__name__]();
for(var prop in json) {
if(!json.hasOwnProperty(prop)) {
continue;
}
if(typeof json[prop] === 'object') {
instance[prop] = deserialize(json[prop], environment);
} else {
instance[prop] = json[prop];
}
}
return instance;
}
var json = {
__name__: "ExampleClass",
mainId: 42,
firstMember: {
__name__: "Member",
id: 1337
},
secondMember: {
__name__: "Member",
id: -1
}
};
var instance = deserialize(json, Environment);
console.log(instance);
Run Code Online (Sandbox Code Playgroud)
如上所述,类成员的类型信息在运行时不可用 - 除非我们使其可用.我们只需要为非原始成员这样做,我们很高兴:
interface Deserializable {
getTypes(): Object;
}
class Member implements Deserializable {
id: number;
getTypes() {
// since the only member, id, is primitive, we don't need to
// return anything here
return {};
}
}
class ExampleClass implements Deserializable {
mainId: number;
firstMember: Member;
secondMember: Member;
getTypes() {
return {
// this is the duplication so that we have
// run-time type information :/
firstMember: Member,
secondMember: Member
};
}
}
function deserialize(json, clazz) {
var instance = new clazz(),
types = instance.getTypes();
for(var prop in json) {
if(!json.hasOwnProperty(prop)) {
continue;
}
if(typeof json[prop] === 'object') {
instance[prop] = deserialize(json[prop], types[prop]);
} else {
instance[prop] = json[prop];
}
}
return instance;
}
var json = {
mainId: 42,
firstMember: {
id: 1337
},
secondMember: {
id: -1
}
};
var instance = deserialize(json, ExampleClass);
console.log(instance);
Run Code Online (Sandbox Code Playgroud)
2016年1月3日更新:正如@GameAlchemist在评论中指出的那样,从Typescript 1.7开始,下面描述的解决方案可以使用类/属性装饰器以更好的方式编写.
序列化总是一个问题,在我看来,最好的方法是一种不是最短的方式.在所有选项中,这是我更喜欢的,因为类的作者可以完全控制反序列化对象的状态.如果我不得不猜测,我会说所有其他选项,迟早会让你遇到麻烦(除非Javascript提出了一个本地方式来解决这个问题).
实际上,下面的例子没有做到灵活正义.它确实只是复制了类的结构.但是,你必须要记住的是,该类可以完全控制使用它想要控制整个类的状态的任何类型的JSON(你可以计算等等).
interface Serializable<T> {
deserialize(input: Object): T;
}
class Member implements Serializable<Member> {
id: number;
deserialize(input) {
this.id = input.id;
return this;
}
}
class ExampleClass implements Serializable<ExampleClass> {
mainId: number;
firstMember: Member;
secondMember: Member;
deserialize(input) {
this.mainId = input.mainId;
this.firstMember = new Member().deserialize(input.firstMember);
this.secondMember = new Member().deserialize(input.secondMember);
return this;
}
}
var json = {
mainId: 42,
firstMember: {
id: 1337
},
secondMember: {
id: -1
}
};
var instance = new ExampleClass().deserialize(json);
console.log(instance);
Run Code Online (Sandbox Code Playgroud)
Joh*_*isz 33
TLDR:TypedJSON(概念的工作证明)
这个问题复杂性的根源在于我们需要在运行时使用仅在编译时存在的类型信息来反序列化JSON .这要求类型信息以某种方式在运行时可用.
幸运的是,这可以通过装饰器和ReflectDecorators以非常优雅和强大的方式解决:
通过ReflectDecorators和属性装饰器的组合,可以轻松记录关于属性的类型信息.这种方法的基本实施将是:
function JsonMember(target: any, propertyKey: string) {
var metadataFieldKey = "__propertyTypes__";
// Get the already recorded type-information from target, or create
// empty object if this is the first property.
var propertyTypes = target[metadataFieldKey] || (target[metadataFieldKey] = {});
// Get the constructor reference of the current property.
// This is provided by TypeScript, built-in (make sure to enable emit
// decorator metadata).
propertyTypes[propertyKey] = Reflect.getMetadata("design:type", target, propertyKey);
}
Run Code Online (Sandbox Code Playgroud)
对于任何给定的属性,上面的代码片段会将属性的构造函数的引用添加到__propertyTypes__
类原型的hidden 属性中.例如:
class Language {
@JsonMember // String
name: string;
@JsonMember// Number
level: number;
}
class Person {
@JsonMember // String
name: string;
@JsonMember// Language
language: Language;
}
Run Code Online (Sandbox Code Playgroud)
就是这样,我们在运行时拥有所需的类型信息,现在可以对其进行处理.
我们首先需要Object
使用JSON.parse
- 获得一个实例- 之后,我们可以遍历__propertyTypes__
(上面收集的)entires 并相应地实例化所需的属性.必须指定根对象的类型,以便反序列化器具有起始点.
同样,这种方法的简单实现将是:
function deserialize<T>(jsonObject: any, Constructor: { new (): T }): T {
if (!Constructor || !Constructor.prototype.__propertyTypes__ || !jsonObject || typeof jsonObject !== "object") {
// No root-type with usable type-information is available.
return jsonObject;
}
// Create an instance of root-type.
var instance: any = new Constructor();
// For each property marked with @JsonMember, do...
Object.keys(Constructor.prototype.__propertyTypes__).forEach(propertyKey => {
var PropertyType = Constructor.prototype.__propertyTypes__[propertyKey];
// Deserialize recursively, treat property type as root-type.
instance[propertyKey] = deserialize(jsonObject[propertyKey], PropertyType);
});
return instance;
}
Run Code Online (Sandbox Code Playgroud)
var json = '{ "name": "John Doe", "language": { "name": "en", "level": 5 } }';
var person: Person = deserialize(JSON.parse(json), Person);
Run Code Online (Sandbox Code Playgroud)
上述想法具有通过预期类型(对于复杂/对象值)而不是JSON中存在的类型进行反序列化的巨大优势.如果Person
是a,那么它是一个Person
创建的实例.通过对原始类型和数组采取一些额外的安全措施,可以使这种方法安全,抵御任何恶意JSON.
但是,如果你现在高兴的是,解决方案是简单的,我有一些坏消息:有一个广阔的需要被照顾的边缘情况数.其中只有一些是:
如果你不想摆弄所有这些(我打赌你不这样做),我很乐意推荐使用这种方法的TypedJSON概念验证的工作实验版本- 我创造了解决这个确切的问题,这是我每天面对的一个问题.
由于装饰器仍然被认为是实验性的,我不建议将它用于生产用途,但到目前为止它对我很有帮助.
xen*_*ide 33
你可以使用Object.assign
我不知道什么时候添加它,我目前正在使用Typescript 2.0.2,这似乎是一个ES6功能.
client.fetch( '' ).then( response => {
return response.json();
} ).then( json => {
let hal : HalJson = Object.assign( new HalJson(), json );
log.debug( "json", hal );
Run Code Online (Sandbox Code Playgroud)
这里的 HalJson
export class HalJson {
_links: HalLinks;
}
export class HalLinks implements Links {
}
export interface Links {
readonly [text: string]: Link;
}
export interface Link {
readonly href: URL;
}
Run Code Online (Sandbox Code Playgroud)
这就是铬所说的
HalJson {_links: Object}
_links
:
Object
public
:
Object
href
:
"http://localhost:9000/v0/public
Run Code Online (Sandbox Code Playgroud)
所以你可以看到它没有递归地分配
And*_*dré 12
我一直在用这个人做这个工作:https://github.com/weichx/cerialize
它非常简单但功能强大.它支持:
例:
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);
Run Code Online (Sandbox Code Playgroud)
这是我的方法(非常简单):
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...
}
Run Code Online (Sandbox Code Playgroud)
我创建了产生打字稿接口和一个运行时“型图”进行对抗的结果运行时类型检查的工具JSON.parse
:ts.quicktype.io
例如,给定此JSON:
{
"name": "David",
"pets": [
{
"name": "Smoochie",
"species": "rhino"
}
]
}
Run Code Online (Sandbox Code Playgroud)
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",
},
};
Run Code Online (Sandbox Code Playgroud)
然后我们JSON.parse
对照类型图检查结果:
export function fromJson(json: string): Person {
return cast(JSON.parse(json), object("Person"));
}
Run Code Online (Sandbox Code Playgroud)
我省略了一些代码,但是您可以尝试使用quicktype以获得详细信息。
对于简单的对象,我喜欢这种方法:
class Person {
constructor(
public id: String,
public name: String,
public title: String) {};
static deserialize(input:any): Person {
return new Person(input.id, input.name, input.title);
}
}
var person = Person.deserialize({id: 'P123', name: 'Bob', title: 'Mr'});
Run Code Online (Sandbox Code Playgroud)
利用在构造函数中定义属性的能力让它变得简洁。
这将为您提供一个类型化的对象(与使用 Object.assign 或某些变体的所有答案相比,这些答案为您提供一个对象)并且不需要外部库或装饰器。
归档时间: |
|
查看次数: |
197200 次 |
最近记录: |