Ini adalah beberapa bidikan cepat untuk menunjukkan beberapa cara berbeda. Mereka sama sekali tidak "lengkap" dan sebagai penafian, saya tidak berpikir itu ide yang baik untuk melakukannya seperti ini. Juga kodenya tidak terlalu bersih karena saya baru saja mengetikkannya agak cepat.
Juga sebagai catatan: Tentu saja kelas deserializable perlu memiliki konstruktor default seperti halnya dalam semua bahasa lain di mana saya mengetahui deserialisasi dalam bentuk apa pun. Tentu saja, Javascript tidak akan mengeluh jika Anda memanggil konstruktor non-default tanpa argumen, tetapi kelas sebaiknya disiapkan untuk itu kemudian (ditambah, itu tidak akan benar-benar menjadi "cara mengetik naskah").
Opsi # 1: Tidak ada informasi run-time sama sekali
Masalah dengan pendekatan ini adalah sebagian besar nama anggota harus sesuai dengan kelasnya. Yang secara otomatis membatasi Anda untuk satu anggota dengan tipe yang sama per kelas dan melanggar beberapa aturan praktik yang baik. Saya sangat menyarankan ini, tetapi hanya daftar di sini karena itu adalah "draft" pertama ketika saya menulis jawaban ini (yang juga mengapa nama-namanya adalah "Foo" dll).
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);
Opsi # 2: Properti nama
Untuk menghilangkan masalah pada opsi # 1, kita perlu memiliki beberapa jenis informasi tentang jenis simpul di objek JSON. Masalahnya adalah bahwa dalam Typescript, hal-hal ini adalah konstruksi waktu kompilasi dan kita membutuhkannya saat runtime - tetapi objek runtime tidak memiliki kesadaran akan sifatnya sampai semuanya ditetapkan.
Salah satu cara untuk melakukannya adalah dengan membuat kelas mengetahui nama mereka. Anda membutuhkan properti ini di JSON juga. Sebenarnya, Anda hanya membutuhkannya di 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);
Opsi # 3: Secara eksplisit menyatakan jenis anggota
Seperti yang dinyatakan di atas, informasi tipe anggota kelas tidak tersedia saat runtime - kecuali kita membuatnya tersedia. Kami hanya perlu melakukan ini untuk anggota non-primitif dan kami senang melakukannya:
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);
Opsi # 4: Cara verbose, tapi rapi
Pembaruan 01/03/2016: Seperti yang ditunjukkan oleh @GameAlchemist dalam komentar ( ide , implementasi ), pada Typecript 1.7, solusi yang dijelaskan di bawah ini dapat ditulis dengan cara yang lebih baik menggunakan dekorator kelas / properti.
Serialisasi selalu menjadi masalah dan menurut saya, cara terbaik adalah cara yang tidak terpendek. Dari semua opsi, inilah yang saya lebih suka karena penulis kelas memiliki kontrol penuh atas keadaan objek deserialized. Jika saya harus menebak, saya akan mengatakan bahwa semua opsi lain, cepat atau lambat, akan membuat Anda dalam kesulitan (kecuali Javascript muncul dengan cara asli untuk menangani ini).
Sungguh, contoh berikut ini tidak adil fleksibilitas. Itu benar-benar hanya menyalin struktur kelas. Perbedaan yang harus Anda ingat di sini adalah bahwa kelas memiliki kontrol penuh untuk menggunakan segala jenis JSON yang ingin mengontrol keadaan seluruh kelas (Anda dapat menghitung hal-hal, dll.).
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);