これらは、いくつかの異なる方法を示すための、ここでのいくつかのクイックショットです。それらは決して「完全」ではなく、免責事項として、私はこのようにすることは良い考えではないと思います。また、コードをすぐに一緒に入力しただけなので、コードはあまりクリーンではありません。
また、メモとして:もちろん、逆シリアル化可能なクラスには、あらゆる種類の逆シリアル化を知っている他のすべての言語の場合と同様に、デフォルトのコンストラクターが必要です。もちろん、引数なしでデフォルト以外のコンストラクターを呼び出しても、Javascriptは文句を言わないでしょうが、クラスはそのために準備された方がいいです(さらに、それは実際には「タイプスクリプトの方法」ではありません)。
オプション#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:nameプロパティ
オプション#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);