Jak zainicjować obiekt TypeScript za pomocą obiektu JSON

Otrzymuję obiekt JSON z połączenia AJAX na serwer REST. Ten obiekt ma nazwy właściwości, które pasują do mojej klasy maszynopisu(jest to kontynuacja tego pytania ).

Jaki jest najlepszy sposób inicjalizacji? Nie sądzę, aby to zadziałało, ponieważ Klasa (obiekt& JSON) ma członków, które są listami obiektów i członków, które są klasami, a te klasy mają członków, które są listami i / lub klasami.

Ale wolałbym podejście, które szuka członka nazwy i przypisywanie ich do siebie, tworzenie list i tworzenie instancji klas w razie potrzeby, więc nie muszę pisać jawnego kodu dla każdego członka w każdej klasie (jest ich dużo!)

Author: wonea, 2014-04-05

12 answers

Oto kilka szybkich ujęć, aby pokazać kilka różnych sposobów. Nie są one bynajmniej "kompletne" i jako zastrzeżenie, nie sądzę, że to dobry pomysł, aby to zrobić w ten sposób. Również kod nie jest zbyt czysty, ponieważ po prostu wpisałem go razem dość szybko.

Również jako Uwaga: oczywiście klasy deserializable muszą mieć domyślne konstruktory, jak to ma miejsce we wszystkich innych językach, w których jestem świadomy deserializacji dowolnego rodzaju. Oczywiście, Javascript nie będzie narzekać, jeśli wywołasz non-default konstruktor bez argumentów, ale klasa powinna być do tego przygotowana (poza tym nie byłaby to tak naprawdę "typescripty").

Opcja #1: Brak informacji o czasie wykonywania

Problem z tym podejściem polega głównie na tym, że nazwa każdego członka musi pasować do jego klasy. Co automatycznie ogranicza cię do jednego członka tego samego typu na klasę i łamie kilka zasad dobrej praktyki. Zdecydowanie odradzam, ale wymień to tutaj, bo to był pierwszy "szkic" kiedy pisałem tej odpowiedzi (dlatego też nazwy są " Foo " itp.).

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

Opcja # 2: Nazwa właściwość

Aby pozbyć się problemu w opcji # 1, musimy mieć jakieś informacje o tym, jakiego typu jest węzeł w obiekcie JSON. Problem polega na tym, że w Typescripcie są to konstrukcje kompilowane i potrzebujemy ich w trybie runtime-ale obiekty runtime po prostu nie mają świadomości swoich właściwości, dopóki nie zostaną ustawione.

Jednym ze sposobów jest uświadomienie klasom ich imion. Potrzebujesz też tej nieruchomości w JSON. W rzeczywistości, ty tylko potrzebujesz go w 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);

Opcja # 3: jawne określanie typów członków

Jak wspomniano powyżej, informacje o typie członków klasy nie są dostępne w czasie wykonywania – czyli chyba, że je udostępnimy. Musimy to zrobić tylko dla nie-prymitywnych członków i możemy iść: {]}

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

Opcja # 4: wyrazisty, ale schludny sposób

Aktualizacja 01/03/2016: Jako @GameAlchemist wskazał w komentarzach, że od Typescript 1.7 rozwiązanie opisane poniżej można napisać w lepszy sposób używając dekoratorów klas/właściwości.

Serializacja jest zawsze problemem i moim zdaniem najlepszym sposobem jest sposób, który po prostu nie jest najkrótszy. Ze wszystkich opcji, to jest to, co wolałbym, ponieważ autor klasy ma pełną kontrolę nad stanem deserializowanych obiektów. Gdybym miał zgadywać, powiedziałbym, że wszystkie inne opcje, prędzej czy później, doprowadzą cię do problemy (chyba, że Javascript wymyśli natywny sposób radzenia sobie z tym).

Naprawdę, poniższy przykład nie oddaje sprawiedliwości elastyczności. To naprawdę kopiuje strukturę klasy. Różnica, o której musisz pamiętać, jest taka, że klasa ma pełną kontrolę nad używaniem dowolnego JSON, który chce kontrolować stan całej klasy (możesz obliczać rzeczy itp.).

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);
 157
Author: Ingo Bürk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-01 22:43:49

TLDR: TypedJSON (working proof of concept)


Źródłem złożoności tego problemu jest to, że musimy deserializować JSON w runtime używając informacji typu, które istnieją tylko w podczas kompilacji. Wymaga to, aby informacje o typie były w jakiś sposób dostępne w czasie wykonywania.

Na szczęście można to rozwiązać w bardzo elegancki i solidny sposób za pomocą dekoratorów i ReflectDecorators :

  1. użyj dekoratorów właściwości na właściwościach, które podlegają serializacji, aby zapisać informacje o metadanych i przechowywać te informacje gdzieś, na przykład na prototypie klasy
  2. W ten sposób można uzyskać dostęp do informacji o metadanych.]}

 

Typ Zapisu-Informacja

Z kombinacją ReflectDecorators I property decorators, informacje typu mogą być łatwo zapisane o nieruchomości. Podstawową implementacją tego podejścia byłoby:

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

Dla danej właściwości, powyższy fragment doda odniesienie do funkcji konstruktora właściwości do ukrytej właściwości __propertyTypes__ w prototypie klasy. Na przykład:

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

    @JsonMember// Number
    level: number;
}

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

    @JsonMember// Language
    language: Language;
}

I to wszystko, mamy wymagane informacje typu w czasie wykonywania, które mogą być teraz przetwarzane.

 

Typ Przetwarzania-Informacja

Najpierw musimy uzyskać Object instancja za pomocą JSON.parse -- Następnie możemy iterację nad całkami w __propertyTypes__ (zebranymi powyżej) i odpowiednio utworzyć instancję wymaganych właściwości. Należy określić typ obiektu głównego, aby deserializer miał punkt początkowy.

[[10]} ponownie, martwą prostą implementacją tego podejścia byłoby:
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);

Powyższa idea ma dużą zaletę deserializacji przez oczekiwane typy (dla wartości złożonych/obiektowych), zamiast tego, co jest obecne w JSON. Jeśli oczekuje się Person, to jest to instancja Person, która zostanie utworzona. Z pewnymi dodatkowymi środkami bezpieczeństwa dla prymitywnych typów i tablic, takie podejście może być zabezpieczone, że odporne any złośliwy JSON.

 

Edge Cases

Jednakże, jeśli jesteś teraz zadowolony, że rozwiązanie jest że proste, mam złe wieści: istnieje Ogromna liczba przypadków krawędzi, które należy zająć. Tylko niektóre z nich są:

  • tablice i elementy tablicy (szczególnie w tablicach zagnieżdżonych)
  • polimorfizm
  • klasy abstrakcyjne i interfejsy
  • ...

Jeśli nie chcesz bawić się tymi wszystkimi (założę się, że nie), z przyjemnością polecam działającą eksperymentalną wersję proof-of-concept wykorzystującą to podejście, TypedJSON -- który stworzyłem, aby rozwiązać ten dokładny problem, problem, z którym stykam się codziennie.

Ze względu na to, jak dekoratorzy są nadal uważane za eksperymentalne, nie polecam używania go do użytku produkcyjnego, ale do tej pory dobrze mi służył.

 29
Author: John Weisz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-02 13:09:46

Możesz użyć Object.assign Nie wiem kiedy to zostało dodane, obecnie używam Typescript 2.0.2, a to wygląda na funkcję ES6.

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

Oto HalJson

export class HalJson {
    _links: HalLinks;
}

export class HalLinks implements Links {
}

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

export interface Link {
    readonly href: URL;
}

Oto co mówi chrome

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

Więc widać, że nie robi przypisywania rekurencyjnie

 25
Author: xenoterracide,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-05 02:54:22

Wykorzystałem tego gościa do pracy: https://github.com/weichx/cerialize

To bardzo proste, ale potężne. Obsługuje:
  • serializacja i deserializacja całego drzewa obiektów.
  • trwałe i przejściowe właściwości tego samego obiektu.
  • Hooki do dostosowania logiki serializacji (de).
  • może (de)serializować do istniejącej instancji (świetne dla Angular) lub generować nowe przypadki.
  • itd.

Przykład:

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);
 10
Author: André,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-18 13:07:27

Opcja # 5: używanie konstruktorów maszynopisu i jQuery.extend

To wydaje się być najbardziej utrzymywalną metodą: dodać konstruktor, który przyjmuje jako parametr strukturę json i rozszerzyć obiekt json. W ten sposób można przetworzyć strukturę json do całego modelu aplikacji.

Nie ma potrzeby tworzenia interfejsów ani listowania właściwości w konstruktorze.

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

W Twoim wywołaniu zwrotnym ajax, w którym otrzymujesz firmę do obliczania wynagrodzeń:

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

   // call the methods on your newCompany object ...
   newCompany.calculateSalaries()
}
 2
Author: Anthony Brenelière,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-06 08:47:11

4. opcja opisana powyżej jest prostym i przyjemnym sposobem, aby to zrobić, który musi być połączony z 2. opcją w przypadku, gdy musisz obsługiwać hierarchię klas, jak na przykład lista członków, która jest którymkolwiek z wystąpień podklas członka Super klasy, np. Director extends Member lub Student extends Member. W takim przypadku musisz podać typ podklasy w formacie json

 1
Author: Xavier Méhaut,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-02 03:29:52

Stworzyłem narzędzie, które generuje Interfejsy TypeScript i runtime "type map" do wykonywania Runtime typechecking na podstawie wyników JSON.parse: ts.quicktype.io

Na przykład, biorąc pod uwagę ten JSON:

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

Quicktype tworzy następujący interfejs TypeScript i mapę typów:

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",
    },
};

Następnie sprawdzamy wynik JSON.parse na mapie typu:

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

Pominąłem trochę kodu, ale możesz spróbować quicktype po szczegóły.

 1
Author: David Siegel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-09 01:14:11

Może nie rzeczywiste, ale proste rozwiązanie:

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

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

Pracuj także nad trudnymi zależnościami!!!

 0
Author: Михайло Пилип,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-28 09:30:14

JQuery .extend robi to za ciebie:

var mytsobject = new mytsobject();

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

$.extend(mytsobject, newObj); //mytsobject will now contain a & b
 0
Author: Daniel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-12 14:55:10

Inna opcja przy użyciu fabryk

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

Użyj TAK

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. sprawia, że Twoje zajęcia są proste
  2. Wtrysk Dostępny dla fabryk dla elastyczności
 0
Author: Anthony Johnston,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-13 21:50:50

Możesz zrobić jak poniżej

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

I

var instance: Instance = <Instance>({
      id: null,
      name: '',
      type: ''
    });
 -1
Author: Md Ayub Ali Sarker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-19 16:10:04
**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;
    }
}
 -1
Author: user8390810,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-27 04:15:43