1. ホーム

[解決済み】JSON-ObjectでTypeScriptオブジェクトを初期化する方法は?

2022-04-02 14:02:56

質問

RESTサーバーへのAJAX呼び出しから、JSONオブジェクトを受信します。このオブジェクトは、私のTypeScriptクラスと一致するプロパティ名を持っています(これは、以下の続きです)。 この質問 ).

初期化はどのようにすればよいのでしょうか?私は これ なぜなら、クラス (& JSON オブジェクト) はオブジェクトのリストであるメンバーとクラスであるメンバーを持っており、それらのクラスはリストおよび/またはクラスであるメンバーを持っているからです。

しかし、私はメンバー名を調べて、必要に応じてリストを作成し、クラスをインスタンス化し、横断的に割り当てるアプローチを好みます。

どのように解決するのか?

これらは、いくつかの異なる方法を示すために、いくつかの簡単なショットです。これらは決して完全なものではありませんし、免責事項として、このようにすることが良いアイデアだとは思いません。また、私はただ素早く入力しただけなので、コードはあまりきれいではありません。

もちろん、デシリアライズ可能なクラスはデフォルトのコンストラクタを持つ必要があり、これは私がデシリアライズについて知っている他のすべての言語のケースと同じです。もちろん、Javascriptは引数なしでデフォルトでないコンストラクタを呼び出しても文句は言いませんが、クラスはそれに対して準備した方が良いでしょう(さらに、それは本当にquot;typescripty way"ではないでしょう)。

オプション #1: ランタイム情報を全く与えない

この方法の問題点は、どのメンバーもそのクラスと同じ名前でなければならないことです。このため、1つのクラスにつき同じ型のメンバは1つに制限され、良い習慣のいくつかのルールを破ることになります。私はこれに対して強く忠告しますが、この回答を書いたときに最初の"draft"だったので、ここに記載します(名前が "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: 冗長ではあるが、きちんとした方法

2016年03月01日に更新しました。 コメントで@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);