Bagaimana cara menginisialisasi objek TypeScript dengan objek JSON


198

Saya menerima objek JSON dari panggilan AJAX ke server REST. Objek ini memiliki nama properti yang cocok dengan kelas TypeScript saya (ini merupakan lanjutan dari pertanyaan ini ).

Apa cara terbaik untuk menginisialisasi itu? Saya tidak berpikir ini akan berhasil karena kelas (& objek JSON) memiliki anggota yang daftar objek dan anggota yang merupakan kelas, dan kelas-kelas tersebut memiliki anggota yang daftar dan / atau kelas.

Tetapi saya lebih suka pendekatan yang mencari nama anggota dan menugaskan mereka di seluruh, membuat daftar dan membuat instance kelas yang diperlukan, jadi saya tidak perlu menulis kode eksplisit untuk setiap anggota di setiap kelas (ada BANYAK!)


1
Mengapa Anda bertanya ini lagi (seperti jawaban yang saya berikan dalam pertanyaan lain mengatakan ini tidak akan berhasil dan itu tentang menyalin properti ke objek yang ada)?
WiredPrairie


3
@WiredPrairie pertanyaan ini berbeda, itu menanyakan apakah saya bisa berjalan properti satu per satu dan menugaskan mereka. Pertanyaan lain adalah bertanya apakah saya bisa melemparkannya.
David Thielen

1
@WiredPrairie cont: Jika Anda terus menyelam ke properti sampai Anda mendapatkan hanya tipe primitif, maka mereka dapat ditugaskan di.
David Thielen

2
Ini masih menyalin semua nilai seperti yang saya sarankan Anda perlu lakukan. Tidak ada cara baru untuk melakukan ini dalam TypeScript karena ini adalah desain dasar JavaScript. Untuk objek besar, Anda mungkin tidak ingin menyalin nilai apa pun dan hanya "bertindak" pada struktur data saja.
WiredPrairie

Jawaban:


188

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

12
Opsi # 4 adalah apa yang saya sebut cara yang masuk akal untuk pergi. Anda masih perlu menulis kode deserialization, tetapi berada di kelas yang sama dan sepenuhnya dapat dikontrol. Jika Anda berasal dari Jawa, maka ini sebanding dengan keharusan menulis equalsatau toStringmetode (hanya itu yang biasanya Anda buat secara otomatis). Seharusnya tidak terlalu sulit untuk menulis generator deserializejika Anda mau, tetapi itu tidak bisa menjadi otomatisasi run-time.
Ingo Bürk

2
@ IngoBürk, saya tahu saya mengajukan pertanyaan ini 2 tahun kemudian, tetapi bagaimana ini akan bekerja pada array objek? Kode contoh di atas berfungsi dengan baik untuk objek JSON. bagaimana bisa digunakan untuk berbagai objek?
Pratik Gaikwad

2
Komentar sampingan: sejak 1.7, (diakui lebih baru dari jawaban Anda), naskah huruf menyediakan dekorator kelas / properti yang memungkinkan untuk menulis solusi ke-4 dengan cara yang lebih rapi.
GameAlchemist

1
Dokumentasi terbaik yang saya temukan adalah jawaban StackOverflow: stackoverflow.com/a/29837695/856501 . Saya menggunakan dekorator dalam proyek saya, dan meskipun saya ingin beberapa fitur lain saya harus mengatakan mereka bekerja seperti pesona.
GameAlchemist

2
Saya tidak akan melompat pada dekorator untuk proyek produksi dulu - perlu diingat mereka masih fitur eksperimental. Saya tidak akan mendasarkan kode dunia nyata pada "eksperimen" karena sejauh yang kami ketahui mereka akan hilang dalam versi berikutnya dan Anda harus menulis ulang banyak kode atau selamanya terjebak pada versi TS lama. Hanya $ 0,02
RVP

35

Anda dapat menggunakan Object.assignSaya tidak tahu kapan ini ditambahkan, saya saat ini menggunakan Typecript 2.0.2, dan ini tampaknya merupakan fitur ES6.

client.fetch( '' ).then( response => {
        return response.json();
    } ).then( json => {
        let hal : HalJson = Object.assign( new HalJson(), json );
        log.debug( "json", hal );

ini dia HalJson

export class HalJson {
    _links: HalLinks;
}

export class HalLinks implements Links {
}

export interface Links {
    readonly [text: string]: Link;
}

export interface Link {
    readonly href: URL;
}

inilah yang dikatakan chrome

HalJson {_links: Object}
_links
:
Object
public
:
Object
href
:
"http://localhost:9000/v0/public

sehingga Anda dapat melihatnya tidak melakukan penugasan secara rekursif


2
sehingga, pada dasarnya, itu ini: Object.assign. Mengapa kita memiliki dua jawaban seperti leksikon di atas yang ini?
phil294

18
@Blauhim Karena Object.assigntidak akan bekerja secara rekursif, dan tidak instantiate jenis objek yang benar, meninggalkan nilai sebagai Objectinstance. Sementara itu baik untuk tugas-tugas sepele, serialisasi tipe kompleks tidak mungkin dengannya. Misalnya, jika properti kelas adalah tipe kelas khusus, JSON.parse+ Object.assignakan membuat properti itu menjadi Object. Efek samping termasuk metode dan asesoris yang hilang.
John Weisz

@ JohnWeisz kelas atas objek menetapkan memang memiliki tipe yang benar, dan saya menyebutkan hal rekursif dalam hal ini ... yang mengatakan, YMMV, dan itu mungkin merupakan pemecah kesepakatan.
xenoterracide

Dikutip langsung dari pertanyaan: "kelas memiliki anggota yang daftar objek dan anggota yang kelas, dan kelas-kelas yang memiliki anggota yang daftar dan / atau kelas [...] Saya lebih suka pendekatan yang mencari anggota nama dan menugaskan mereka di seluruh, membuat daftar dan membuat instance kelas sesuai kebutuhan, jadi saya tidak perlu menulis kode eksplisit untuk setiap anggota di setiap kelas " - yang tidak demikian halnya Object.assign, di mana ia masih harus menulis instantiasi bersarang oleh tangan. Pendekatan ini baik untuk objek tingkat tutorial yang sangat sederhana, tetapi tidak untuk penggunaan nyata.
John Weisz

@JohnWeisz yakin, sebagian besar menjawab dengan ini karena tidak ada jawaban dan tampaknya sederhana untuk beberapa kasus penggunaan. Saya yakin itu juga dapat digunakan dalam kombinasi dengan jawaban lain seperti refleksi, untuk melakukan apa yang Anda cari. Saya juga menulisnya sebagian sehingga saya akan mengingatnya nanti. Melihat jawaban-jawaban ini dan telah menggunakan dan menulis perpustakaan yang jauh lebih kuat, tampaknya tidak ada yang tersedia untuk "penggunaan nyata".
xenoterracide

34

TLDR: TypedJSON (bukti kerja konsep)


Akar dari kerumitan masalah ini adalah bahwa kita perlu melakukan deserialisasi JSON saat runtime menggunakan tipe informasi yang hanya ada pada waktu kompilasi . Ini mengharuskan jenis-informasi entah bagaimana dibuat tersedia saat runtime.

Untungnya, ini dapat diselesaikan dengan cara yang sangat elegan dan kuat dengan dekorator dan ReflectDecorators :

  1. Gunakan dekorator properti pada properti yang tunduk pada serialisasi, untuk merekam informasi metadata dan menyimpan informasi itu di suatu tempat, misalnya pada prototipe kelas
  2. Umpan informasi metadata ini ke penginisialisasi rekursif (deserializer)

 

Jenis Perekaman-Informasi

Dengan kombinasi ReflectDecorators dan dekorator properti, ketik informasi dapat dengan mudah direkam tentang sebuah properti. Implementasi yang belum sempurna dari pendekatan ini adalah:

function JsonMember(target: any, propertyKey: string) {
    var metadataFieldKey = "__propertyTypes__";

    // Get the already recorded type-information from target, or create
    // empty object if this is the first property.
    var propertyTypes = target[metadataFieldKey] || (target[metadataFieldKey] = {});

    // Get the constructor reference of the current property.
    // This is provided by TypeScript, built-in (make sure to enable emit
    // decorator metadata).
    propertyTypes[propertyKey] = Reflect.getMetadata("design:type", target, propertyKey);
}

Untuk properti apa pun yang diberikan, cuplikan di atas akan menambahkan referensi fungsi konstruktor properti ke __propertyTypes__properti tersembunyi pada prototipe kelas. Sebagai contoh:

class Language {
    @JsonMember // String
    name: string;

    @JsonMember// Number
    level: number;
}

class Person {
    @JsonMember // String
    name: string;

    @JsonMember// Language
    language: Language;
}

Dan itu saja, kami memiliki informasi jenis yang diperlukan saat runtime, yang sekarang dapat diproses.

 

Jenis-Informasi Pemrosesan

Pertama-tama kita perlu mendapatkan sebuah Objectinstance menggunakan JSON.parse- setelah itu, kita dapat melakukan iterate atas entires di __propertyTypes__(dikumpulkan di atas) dan instantiate properti yang diperlukan sesuai. Jenis objek root harus ditentukan, sehingga deserializer memiliki titik awal.

Sekali lagi, implementasi sederhana dari pendekatan ini adalah:

function deserialize<T>(jsonObject: any, Constructor: { new (): T }): T {
    if (!Constructor || !Constructor.prototype.__propertyTypes__ || !jsonObject || typeof jsonObject !== "object") {
        // No root-type with usable type-information is available.
        return jsonObject;
    }

    // Create an instance of root-type.
    var instance: any = new Constructor();

    // For each property marked with @JsonMember, do...
    Object.keys(Constructor.prototype.__propertyTypes__).forEach(propertyKey => {
        var PropertyType = Constructor.prototype.__propertyTypes__[propertyKey];

        // Deserialize recursively, treat property type as root-type.
        instance[propertyKey] = deserialize(jsonObject[propertyKey], PropertyType);
    });

    return instance;
}
var json = '{ "name": "John Doe", "language": { "name": "en", "level": 5 } }';
var person: Person = deserialize(JSON.parse(json), Person);

Ide di atas memiliki keuntungan besar deserializing oleh tipe yang diharapkan (untuk nilai kompleks / objek), bukan apa yang ada di JSON. Jika a Persondiharapkan, maka itu adalah Personcontoh yang dibuat. Dengan beberapa langkah-langkah keamanan tambahan di tempat untuk tipe primitif dan array, pendekatan ini dapat dibuat aman, yang tahan setiap JSON berbahaya.

 

Kasus Tepi

Namun, jika Anda sekarang senang bahwa solusinya adalah bahwa sederhana, saya memiliki beberapa berita buruk: ada luas sejumlah kasus tepi yang perlu diurus. Hanya beberapa di antaranya adalah:

  • Array dan elemen array (terutama dalam array bersarang)
  • Polimorfisme
  • Kelas dan antarmuka abstrak
  • ...

Jika Anda tidak ingin bermain-main dengan semua ini (saya yakin Anda tidak), saya akan dengan senang hati merekomendasikan versi percobaan yang berfungsi dari pembuktian konsep menggunakan pendekatan ini, TypedJSON - yang saya buat untuk mengatasi masalah ini, masalah yang saya hadapi setiap hari.

Karena bagaimana dekorator masih dianggap eksperimental, saya tidak akan merekomendasikan menggunakannya untuk penggunaan produksi, tetapi sejauh ini itu membantu saya dengan baik.


TypedJSON bekerja dengan sangat baik; terima kasih banyak untuk referensi.
Neil

Kerja bagus, Anda telah datang dengan solusi yang sangat elegan untuk masalah yang telah mengganggu saya untuk sementara waktu. Saya akan mengikuti proyek Anda dengan sangat cermat!
John Strickler

12

Saya telah menggunakan orang ini untuk melakukan pekerjaan: https://github.com/weichx/cerialize

Ini sangat sederhana namun kuat. Ini mendukung:

  • Serialisasi & deserialisasi seluruh pohon objek.
  • Properti persisten & sementara pada objek yang sama.
  • Kait untuk menyesuaikan logika serialisasi (de).
  • Ini dapat (de) membuat cerita bersambung menjadi instance yang ada (bagus untuk Angular) atau menghasilkan instance baru.
  • dll.

Contoh:

class Tree {
  @deserialize public species : string; 
  @deserializeAs(Leaf) public leafs : Array<Leaf>;  //arrays do not need extra specifications, just a type.
  @deserializeAs(Bark, 'barkType') public bark : Bark;  //using custom type and custom key name
  @deserializeIndexable(Leaf) public leafMap : {[idx : string] : Leaf}; //use an object as a map
}

class Leaf {
  @deserialize public color : string;
  @deserialize public blooming : boolean;
  @deserializeAs(Date) public bloomedAt : Date;
}

class Bark {
  @deserialize roughness : number;
}

var json = {
  species: 'Oak',
  barkType: { roughness: 1 },
  leafs: [ {color: 'red', blooming: false, bloomedAt: 'Mon Dec 07 2015 11:48:20 GMT-0500 (EST)' } ],
  leafMap: { type1: { some leaf data }, type2: { some leaf data } }
}
var tree: Tree = Deserialize(json, Tree);

6

Saya telah membuat alat yang menghasilkan antarmuka TypeScript dan "ketik peta" runtime untuk melakukan pemeriksaan ketik runtime terhadap hasil JSON.parse: ts.quicktype.io

Misalnya, diberikan JSON ini:

{
  "name": "David",
  "pets": [
    {
      "name": "Smoochie",
      "species": "rhino"
    }
  ]
}

quicktype menghasilkan antarmuka TypeScript berikut dan ketik peta:

export interface Person {
    name: string;
    pets: Pet[];
}

export interface Pet {
    name:    string;
    species: string;
}

const typeMap: any = {
    Person: {
        name: "string",
        pets: array(object("Pet")),
    },
    Pet: {
        name: "string",
        species: "string",
    },
};

Kemudian kami memeriksa hasil JSON.parseterhadap tipe peta:

export function fromJson(json: string): Person {
    return cast(JSON.parse(json), object("Person"));
}

Saya telah meninggalkan beberapa kode, tetapi Anda dapat mencoba mengetik cepat untuk detailnya.


1
Setelah melakukan banyak penelitian dan mencoba beberapa teknik parsing, saya dapat mengatakan ini adalah solusi yang sangat baik - terutama karena dekorator masih eksperimental. * Tautan asli rusak untuk saya; tapi ts.quicktype.io bekerja. * Mengubah JSON ke JSON Schema adalah langkah pertama yang baik.
LexieHankins

3

Opsi # 5: Menggunakan konstruktor Typescript dan jQuery.extend

Ini tampaknya menjadi metode yang paling dipelihara: menambahkan konstruktor yang mengambil sebagai parameter struktur json, dan memperluas objek json. Dengan begitu, Anda dapat mem-parsing struktur json ke dalam seluruh model aplikasi.

Tidak perlu membuat antarmuka, atau mendaftar properti di konstruktor.

export class Company
{
    Employees : Employee[];

    constructor( jsonData: any )
    {
        jQuery.extend( this, jsonData);

        // apply the same principle to linked objects:
        if ( jsonData.Employees )
            this.Employees = jQuery.map( jsonData.Employees , (emp) => {
                return new Employee ( emp );  });
    }

    calculateSalaries() : void { .... }
}

export class Employee
{
    name: string;
    salary: number;
    city: string;

    constructor( jsonData: any )
    {
        jQuery.extend( this, jsonData);

        // case where your object's property does not match the json's:
        this.city = jsonData.town;
    }
}

Dalam panggilan balik ajax Anda tempat Anda menerima perusahaan untuk menghitung gaji:

onReceiveCompany( jsonCompany : any ) 
{
   let newCompany = new Company( jsonCompany );

   // call the methods on your newCompany object ...
   newCompany.calculateSalaries()
}

dari mana $.extenddatang?
whale_steward

@ whale_steward Saya akan menganggap penulis merujuk ke perpustakaan jQuery. Di dunia JavaScript, '$' sangat sering seseorang menggunakan jQuery.
Nick Roth

bagaimana cara mengimpornya? cukup sertakan saja di html head sudah cukup?
whale_steward

ya saya memperbarui jawaban untuk mengganti $ dengan jQuery. impor jQuery.js di kepala html, dan instal dan tambahkan @ types / jquery di package.json Anda, bagian devDependencies.
Anthony Brenelière

1
Perhatikan bahwa dalam Javascript, Anda harus melakukannya Object.assign, yang menghapus ketergantungan ini ke jQuery.
Léon Pelletier

2

Untuk objek sederhana, saya suka metode ini:

class Person {
  constructor(
    public id: String, 
    public name: String, 
    public title: String) {};

  static deserialize(input:any): Person {
    return new Person(input.id, input.name, input.title);
  }
}

var person = Person.deserialize({id: 'P123', name: 'Bob', title: 'Mr'});

Memanfaatkan kemampuan untuk mendefinisikan properti di konstruktor memungkinkannya menjadi singkat.

Ini memberi Anda objek yang diketik (vs semua jawaban yang menggunakan Object.assign atau beberapa varian, yang memberi Anda Obyek) dan tidak memerlukan perpustakaan atau dekorator eksternal.


1

Opsi ke-4 yang dijelaskan di atas adalah cara sederhana dan menyenangkan untuk melakukannya, yang harus dikombinasikan dengan opsi ke-2 dalam kasus di mana Anda harus menangani hierarki kelas seperti misalnya daftar anggota yang merupakan salah satu dari kejadian subkelas dari kelas super Anggota, mis. Direktur memperpanjang Anggota atau Siswa memperpanjang Anggota. Dalam hal ini Anda harus memberikan tipe subclass dalam format json


1

JQuery .extend melakukan ini untuk Anda:

var mytsobject = new mytsobject();

var newObj = {a:1,b:2};

$.extend(mytsobject, newObj); //mytsobject will now contain a & b

1

yang terbaik yang saya temukan untuk tujuan ini adalah transformator kelas. github.com/typestack/class-transformer

Begitulah cara Anda menggunakannya:

Beberapa kelas:

export class Foo {

    name: string;

    @Type(() => Bar)
    bar: Bar;

    public someFunction = (test: string): boolean => {
        ...
    }
}


import { plainToClass } from 'class-transformer';

export class SomeService {

  anyFunction() {
u = plainToClass(Foo, JSONobj);
 }

Jika Anda menggunakan properti bersarang dekorator @Type akan dibuat juga.


0

Mungkin bukan solusi aktual, tetapi sederhana:

interface Bar{
x:number;
y?:string; 
}

var baz:Bar = JSON.parse(jsonString);
alert(baz.y);

bekerja untuk dependensi yang sulit juga !!!


9
Pendekatan ini sebenarnya tidak berfungsi seperti yang diharapkan. Jika Anda memeriksa hasil runtime, bazakan menjadi tipe Objectdan bukan tipe Bar.Ini berfungsi dalam kasus sederhana ini karena Bartidak memiliki metode (hanya properti primitif). Jika Barmemiliki metode seperti isEnabled(), pendekatan ini akan gagal karena metode itu tidak akan berada dalam string JSON berseri.
Todd

0

Pilihan lain menggunakan pabrik

export class A {

    id: number;

    date: Date;

    bId: number;
    readonly b: B;
}

export class B {

    id: number;
}

export class AFactory {

    constructor(
        private readonly createB: BFactory
    ) { }

    create(data: any): A {

        const createB = this.createB.create;

        return Object.assign(new A(),
            data,
            {
                get b(): B {

                    return createB({ id: data.bId });
                },
                date: new Date(data.date)
            });
    }
}

export class BFactory {

    create(data: any): B {

        return Object.assign(new B(), data);
    }
}

https://github.com/MrAntix/ts-deserialize

gunakan seperti ini

import { A, B, AFactory, BFactory } from "./deserialize";

// create a factory, simplified by DI
const aFactory = new AFactory(new BFactory());

// get an anon js object like you'd get from the http call
const data = { bId: 1, date: '2017-1-1' };

// create a real model from the anon js object
const a = aFactory.create(data);

// confirm instances e.g. dates are Dates 
console.log('a.date is instanceof Date', a.date instanceof Date);
console.log('a.b is instanceof B', a.b instanceof B);
  1. membuat kelas Anda sederhana
  2. injeksi tersedia untuk pabrik untuk fleksibilitas

0

Saya pribadi lebih suka opsi # 3 dari @Ingo Bürk. Dan saya memperbaiki kodenya untuk mendukung berbagai data kompleks dan array data primitif.

interface IDeserializable {
  getTypes(): Object;
}

class Utility {
  static deserializeJson<T>(jsonObj: object, classType: any): T {
    let instanceObj = new classType();
    let types: IDeserializable;
    if (instanceObj && instanceObj.getTypes) {
      types = instanceObj.getTypes();
    }

    for (var prop in jsonObj) {
      if (!(prop in instanceObj)) {
        continue;
      }

      let jsonProp = jsonObj[prop];
      if (this.isObject(jsonProp)) {
        instanceObj[prop] =
          types && types[prop]
            ? this.deserializeJson(jsonProp, types[prop])
            : jsonProp;
      } else if (this.isArray(jsonProp)) {
        instanceObj[prop] = [];
        for (let index = 0; index < jsonProp.length; index++) {
          const elem = jsonProp[index];
          if (this.isObject(elem) && types && types[prop]) {
            instanceObj[prop].push(this.deserializeJson(elem, types[prop]));
          } else {
            instanceObj[prop].push(elem);
          }
        }
      } else {
        instanceObj[prop] = jsonProp;
      }
    }

    return instanceObj;
  }

  //#region ### get types ###
  /**
   * check type of value be string
   * @param {*} value
   */
  static isString(value: any) {
    return typeof value === "string" || value instanceof String;
  }

  /**
   * check type of value be array
   * @param {*} value
   */
  static isNumber(value: any) {
    return typeof value === "number" && isFinite(value);
  }

  /**
   * check type of value be array
   * @param {*} value
   */
  static isArray(value: any) {
    return value && typeof value === "object" && value.constructor === Array;
  }

  /**
   * check type of value be object
   * @param {*} value
   */
  static isObject(value: any) {
    return value && typeof value === "object" && value.constructor === Object;
  }

  /**
   * check type of value be boolean
   * @param {*} value
   */
  static isBoolean(value: any) {
    return typeof value === "boolean";
  }
  //#endregion
}

// #region ### Models ###
class Hotel implements IDeserializable {
  id: number = 0;
  name: string = "";
  address: string = "";
  city: City = new City(); // complex data
  roomTypes: Array<RoomType> = []; // array of complex data
  facilities: Array<string> = []; // array of primitive data

  // getter example
  get nameAndAddress() {
    return `${this.name} ${this.address}`;
  }

  // function example
  checkRoom() {
    return true;
  }

  // this function will be use for getting run-time type information
  getTypes() {
    return {
      city: City,
      roomTypes: RoomType
    };
  }
}

class RoomType implements IDeserializable {
  id: number = 0;
  name: string = "";
  roomPrices: Array<RoomPrice> = [];

  // getter example
  get totalPrice() {
    return this.roomPrices.map(x => x.price).reduce((a, b) => a + b, 0);
  }

  getTypes() {
    return {
      roomPrices: RoomPrice
    };
  }
}

class RoomPrice {
  price: number = 0;
  date: string = "";
}

class City {
  id: number = 0;
  name: string = "";
}
// #endregion

// #region ### test code ###
var jsonObj = {
  id: 1,
  name: "hotel1",
  address: "address1",
  city: {
    id: 1,
    name: "city1"
  },
  roomTypes: [
    {
      id: 1,
      name: "single",
      roomPrices: [
        {
          price: 1000,
          date: "2020-02-20"
        },
        {
          price: 1500,
          date: "2020-02-21"
        }
      ]
    },
    {
      id: 2,
      name: "double",
      roomPrices: [
        {
          price: 2000,
          date: "2020-02-20"
        },
        {
          price: 2500,
          date: "2020-02-21"
        }
      ]
    }
  ],
  facilities: ["facility1", "facility2"]
};

var hotelInstance = Utility.deserializeJson<Hotel>(jsonObj, Hotel);

console.log(hotelInstance.city.name);
console.log(hotelInstance.nameAndAddress); // getter
console.log(hotelInstance.checkRoom()); // function
console.log(hotelInstance.roomTypes[0].totalPrice); // getter
// #endregion

-1

Anda bisa melakukannya seperti di bawah ini

export interface Instance {
  id?:string;
  name?:string;
  type:string;
}

dan

var instance: Instance = <Instance>({
      id: null,
      name: '',
      type: ''
    });

Ini sebenarnya tidak akan menghasilkan contoh runtime dari jenis objek yang Anda harapkan. Ini akan terlihat berfungsi ketika tipe Anda hanya memiliki properti primitif, tetapi akan gagal ketika suatu tipe memiliki metode. Definisi antarmuka juga tidak tersedia saat runtime (hanya waktu pembuatan).
Todd

-1
**model.ts**
export class Item {
    private key: JSON;
    constructor(jsonItem: any) {
        this.key = jsonItem;
    }
}

**service.ts**
import { Item } from '../model/items';

export class ItemService {
    items: Item;
    constructor() {
        this.items = new Item({
            'logo': 'Logo',
            'home': 'Home',
            'about': 'About',
            'contact': 'Contact',
        });
    }
    getItems(): Item {
        return this.items;
    }
}

sebut konten seperti contoh di bawah ini:
user8390810

<a class="navbar-brand" href="#"> {{keyItems.key.logo}} </a>
user8390810

Ini sepertinya tidak "membuat kelas seperti yang diperlukan".
LexieHankins
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.