如何使用JSON对象初始化TypeScript对象 [英] How do I initialize a TypeScript object with a JSON object

查看:75
本文介绍了如何使用JSON对象初始化TypeScript对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我从AJAX调用REST服务器收到一个JSON对象.该对象具有与我的TypeScript类匹配的属性名称(这是 不会起作用,因为该类(& JSON对象)具有作为对象列表的成员和作为类的成员,而这些类具有作为列表和/或类的成员.

但是我更喜欢一种查找成员名称并在其间进行分配,根据需要创建列表和实例化类的方法,因此,我不必为每个类中的每个成员编写显式代码(有很多! )

解决方案

这些是一些快速快照,它们显示了几种不同的方式.它们绝不是完整的",作为免责声明,我认为这样做不是一个好主意.代码也不是很干净,因为我只是很快地将它们键入在一起.

还请注意:可反序列化的类当然需要具有默认构造函数,就像其他所有我知道任何反序列化语言的语言一样.当然,如果您调用不带参数的非默认构造函数,则Javascript不会抱怨,但是类最好为此做准备(此外,它实际上不是打字方法").

选项1:完全没有运行时信息

此方法的问题主要是任何成员的名称都必须与其类匹配.这会自动将您限制为每个班级一名相同类型的成员,并破坏了一些良好实践的规则.我强烈建议您这样做,但请在此处列出,因为这是我编写此答案时的第一个草稿"(这也是为什么名称是"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);

选项2:名称属性

要摆脱选项#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);

选项3:明确说明成员类型

如上所述,类成员的类型信息在运行时不可用-除非我们使它可用.我们只需要对非原始成员执行此操作,我们很好:

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);

选项4:详细但整洁的方式

更新01/03/2016:正如@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);

I receive a JSON object from an AJAX call to a REST server. This object has property names that match my TypeScript class (this is a follow-on to this question).

What is the best way to initialize it? I don't think this will work because the class (& JSON object) have members that are lists of objects and members that are classes, and those classes have members that are lists and/or classes.

But I'd prefer an approach that looks up the member names and assigns them across, creating lists and instantiating classes as needed, so I don't have to write explicit code for every member in every class (there's a LOT!)

解决方案

These are some quick shots at this to show a few different ways. They are by no means "complete" and as a disclaimer, I don't think it's a good idea to do it like this. Also the code isn't too clean since I just typed it together rather quickly.

Also as a note: Of course deserializable classes need to have default constructors as is the case in all other languages where I'm aware of deserialization of any kind. Of course, Javascript won't complain if you call a non-default constructor with no arguments, but the class better be prepared for it then (plus, it wouldn't really be the "typescripty way").

Option #1: No run-time information at all

The problem with this approach is mostly that the name of any member must match its class. Which automatically limits you to one member of same type per class and breaks several rules of good practice. I strongly advise against this, but just list it here because it was the first "draft" when I wrote this answer (which is also why the names are "Foo" etc.).

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);

Option #2: The name property

To get rid of the problem in option #1, we need to have some kind of information of what type a node in the JSON object is. The problem is that in Typescript, these things are compile-time constructs and we need them at runtime – but runtime objects simply have no awareness of their properties until they are set.

One way to do it is by making classes aware of their names. You need this property in the JSON as well, though. Actually, you only need it in the 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);

Option #3: Explicitly stating member types

As stated above, the type information of class members is not available at runtime – that is unless we make it available. We only need to do this for non-primitive members and we are good to go:

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);

Option #4: The verbose, but neat way

Update 01/03/2016: As @GameAlchemist pointed out in the comments, as of Typescript 1.7, the solution described below can be written in a better way using class/property decorators.

Serialization is always a problem and in my opinion, the best way is a way that just isn't the shortest. Out of all the options, this is what I'd prefer because the author of the class has full control over the state of deserialized objects. If I had to guess, I'd say that all other options, sooner or later, will get you in trouble (unless Javascript comes up with a native way for dealing with this).

Really, the following example doesn't do the flexibility justice. It really does just copy the class's structure. The difference you have to keep in mind here, though, is that the class has full control to use any kind of JSON it wants to control the state of the entire class (you could calculate things etc.).

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);

这篇关于如何使用JSON对象初始化TypeScript对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆