TypeScript オブジェクトを JSON オブジェクトで初期化するにはどうすればいいですか? 質問する

TypeScript オブジェクトを JSON オブジェクトで初期化するにはどうすればいいですか? 質問する

RESTサーバーへのAJAX呼び出しからJSONオブジェクトを受け取りました。このオブジェクトには、TypeScriptクラスと一致するプロパティ名があります(これはこの質問)。

初期化する最良の方法は何ですか?私はそうは思いませんこれクラス (および JSON オブジェクト) にはオブジェクトのリストであるメンバーとクラスであるメンバーがあり、それらのクラスにはリストやクラスであるメンバーがあるため、機能します。

しかし、メンバー名を検索して割り当て、リストを作成し、必要に応じてクラスをインスタンス化するアプローチの方が好ましいです。そうすれば、すべてのクラスのすべてのメンバーに対して明示的なコードを記述する必要がなくなります (たくさんあります!)。

ベストアンサー1

これらは、いくつかの異なる方法を示すための簡単な例です。これらは決して「完全」なものではなく、免責事項として、このようにするのは良い考えではないと思います。また、コードは、かなり急いで入力しただけなので、あまりきれいではありません。

また、注意点として: もちろん、デシリアライズ可能なクラスには、私が知っているあらゆる種類のデシリアライズのその他の言語の場合と同様に、デフォルトのコンストラクタが必要です。もちろん、Javascript は、引数なしでデフォルト以外のコンストラクタを呼び出してもエラーは発生しませんが、その場合はクラスをそのように準備しておく必要があります (さらに、それは実際には「Typescript 的な方法」ではありません)。

オプション1: 実行時情報を一切含まない

このアプローチの問題は、主に、メンバーの名前がそのクラスと一致しなければならないことです。これにより、クラスごとに同じタイプのメンバーが 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 では、これらのものはコンパイル時の構成要素であり、実行時に必要になるということです。しかし、実行時オブジェクトは、設定されるまでプロパティを認識しません。

これを実現する方法の 1 つは、クラスにその名前を認識させることです。ただし、このプロパティは 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年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);

おすすめ記事