Comment initialiser un objet TypeScript avec un objet JSON

je reçois un objet JSON d'un appel AJAX vers un serveur REST. Cet objet a des noms de propriétés qui correspondent à ma classe TypeScript (ceci est une suite à cette question ).

Quelle est la meilleure façon d'initialiser? Je ne pense pas que ce fonctionnera parce que la classe (& l'objet JSON) a des membres qui sont des listes d'objets et des membres qui sont des classes, et ces classes ont des membres qui sont des listes et/ou des classes.

mais je préférerais une approche qui regarde les noms des membres et les assigne à travers, la création de listes et instanciating classes si nécessaire, de sorte que je n'ai pas à écrire de code explicite pour chaque membre dans chaque classe (il y a beaucoup!)

146
demandé sur wonea 2014-04-05 23:52:35

12 réponses

voici quelques photos rapides à montrer quelques façons différentes. Ils ne sont pas "complet" et comme un avertissement, je ne pense pas que c'est une bonne idée de faire comme cela. Aussi le code n'est pas trop propre puisque je l'ai juste tapé ensemble assez rapidement.

aussi comme note: Bien sûr les classes deserialisables doivent avoir des constructeurs par défaut comme c'est le cas dans toutes les autres langues où je suis conscient de la deserialisation de toute sorte. Bien sûr, Javascript ne se plaindra pas si vous appelez un constructeur non par défaut sans argument, mais la classe A intérêt à être préparée pour cela (en plus, ce ne serait pas vraiment la "manière dactylographiée").

Option #1: Aucune information sur l'heure d'exécution

le problème avec cette approche est principalement que le nom de n'importe quel membre doit correspondre à sa classe. Ce qui vous limite automatiquement à un membre du même type par classe et enfreint plusieurs règles de bonne pratique. Je déconseille fortement ce, mais juste la liste ici parce que c'était le premier "projet" quand j'ai écrit cette réponse (qui est aussi pourquoi les noms sont "Foo", etc.).

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

Option #2: le nom propriété

Pour se débarrasser du problème dans l'option #1, nous avons besoin d'avoir des informations de ce type d'un nœud dans l'objet JSON. Le problème, C'est qu'en Tapescript, ces choses sont des constructions de compilation et nous en avons besoin à l'exécution-mais les objets d'exécution ont simplement pas de connaissance de leurs propriétés tant qu'elles ne sont pas réglées.

une façon de le faire est de faire prendre conscience aux classes de leurs noms. Vous avez besoin de cette propriété dans le JSON aussi, cependant. En fait, vous seulement besoin dans le 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);

Option #3: mention explicite des types de membres

comme indiqué ci – dessus, l'information de type des membres de la classe n'est pas disponible à l'exécution-c'est-à-dire à moins que nous la rendions disponible. Nous il suffit de le faire pour les membres non primitifs et nous sommes bons à aller:

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

Option #4: Le verbeux, mais de façon soignée

mise à jour 01/03/2016: comme @GameAlchemist l'a souligné dans les commentaires, à partir de la version dactylographiée 1.7, la solution décrite ci-dessous peut être écrite d'une meilleure manière en utilisant des décorateurs de classe/propriété.

Sérialisation est toujours un problème et à mon avis, la meilleure façon est d'une manière juste n'est-ce pas le plus court. De toutes les options, c'est ce que je préférerais parce que l'auteur de la classe A le contrôle total sur l'état des objets désérialisés. Si je devais deviner, je dirais que toutes les autres options, tôt ou tard, vont vous mettre en difficulté (à moins que Javascript ne trouve une façon native de traiter cela).

vraiment, l'exemple suivant Ne rend pas justice à la flexibilité. Il ne fait vraiment que Copier la structure de la classe. La différence que vous devez garder l'esprit ici, cependant, est que la classe A le contrôle total d'utiliser n'importe quel type de JSON qu'elle veut contrôler l'état de la classe entière (vous pourriez calculer des choses etc.).

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
répondu Ingo Bürk 2016-03-01 22:43:49

TLDR: TypedJSON (working proof of concept)


la racine de la complexité de ce problème est que nous devons désérialiser JSON à runtime en utilisant des informations de type qui n'existe qu'à compiler time . Cela nécessite que les informations de type soient d'une manière ou d'une autre rendues disponibles à l'exécution.

heureusement, cela peut être résolu d'une manière très élégante et robuste avec décorateurs et Réflectdécorateurs :

  1. Utiliser "1519320920 de la propriété" décorateurs sur les propriétés qui sont soumis à la sérialisation, pour enregistrer les informations de métadonnées et de les stocker quelque part, par exemple sur le prototype de la classe
  2. transmettre cette information de métadonnées à un initialiseur récursif (deserializer)

Type D'Enregistrement-Information

avec une combinaison de ReflectDecorators et de décorateurs de propriété, l'information de type peut être facilement enregistré sur une propriété. Une mise en œuvre rudimentaire de cette approche serait:

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

Pour toute propriété, l'extrait ci-dessus permettra d'ajouter une référence de la fonction constructeur de la propriété à l' propriété cachée __propertyTypes__ sur le prototype de la classe. Par exemple:

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

    @JsonMember// Number
    level: number;
}

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

    @JsonMember// Language
    language: Language;
}

et c'est tout, nous avons les informations de type requises à l'exécution, qui peuvent maintenant être traitées.

Type De Traitement-Information

nous devons d'abord obtenir une instance Object en utilisant JSON.parse -- après cela, nous pouvons itérer sur les entires dans __propertyTypes__ (collecté ci-dessus) et instancier le propriétés requises en conséquence. Le type de l'objet racine doit être spécifié, de sorte que le désérialiseur ait un point de départ.

encore une fois, une simple mise en œuvre morte de cette approche serait:

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

l'idée ci-dessus a un grand avantage de desérialiser par types attendus (pour les valeurs complexes/objet), au lieu de ce qui est présent dans le JSON. Si un Person est prévu, il est un Person instance qui est créée. Avec quelques mesures de sécurité supplémentaires en place pour les types primitifs et les tableaux, cette approche peut être rendue sûre, qui résiste tout JSON malveillant.

Cas Limites

cependant, si vous êtes maintenant heureux que la solution est que simple, j'ai quelques mauvaises nouvelles: il ya un vaste nombre de cas de bord qui besoin d'être pris en charge. Seulement certains d'entre eux sont:

  • Tableaux et éléments de tableaux (spécialement dans les tableaux imbriqués)
  • polymorphisme
  • classes abstraites et interfaces
  • ...

si vous ne voulez pas jouer avec tout cela( je parie que vous ne le faites pas), je serais heureux de recommander une version expérimentale de travail d'une preuve de concept en utilisant cette approche, TypedJSON -- que j'ai créé pour s'attaquer à ce problème précis, un problème auquel je suis confronté quotidiennement.

en raison de la façon dont les décorateurs sont encore considérés comme expérimentaux, Je ne recommande pas de l'utiliser pour la production, mais jusqu'à présent, il m'a bien servi.

29
répondu John Weisz 2017-01-02 13:09:46

vous pouvez utiliser Object.assign Je ne sais pas quand cela a été ajouté, j'utilise actuellement Typescript 2.0.2, et cela semble être une fonctionnalité ES6.

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

voilà HalJson

export class HalJson {
    _links: HalLinks;
}

export class HalLinks implements Links {
}

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

export interface Link {
    readonly href: URL;
}

voici ce que chrome dit qu'il est

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

donc vous pouvez voir qu'il ne fait pas l'affectation récursive

25
répondu xenoterracide 2017-01-05 02:54:22

j'ai utilisé ce type pour faire le travail: https://github.com/weichx/cerialize

c'est très simple mais puissant. Il soutient:

  • la Sérialisation et la désérialisation d'un ensemble de l'arborescence d'objets.
  • "151980920 Permanents et transitoires propriétés sur le même objet.
  • Crochets pour personnaliser la (dé)sérialisation de la logique.
  • il peut (de)sérialiser en instance existante (grande pour L'angle) ou générer de nouvelles instances.
  • etc.

exemple:

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
répondu André 2017-02-18 13:07:27

Option #5: Utilisation de constructeurs dactylographiés et de jQuery.étendre

cela semble être la méthode la plus maintenable: ajouter un constructeur qui prend comme paramètre la structure json, et étendre l'objet json. De cette façon, vous pouvez analyser une structure json dans l'ensemble du modèle d'application.

il n'est pas nécessaire de créer des interfaces, ou de lister des propriétés dans le constructeur.

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

dans votre appel ajax où vous recevez un société pour calculer les salaires:

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

   // call the methods on your newCompany object ...
   newCompany.calculateSalaries()
}
2
répondu Anthony Brenelière 2017-02-06 08:47:11

la 4ème option décrite ci-dessus est une manière simple et agréable de le faire, qui doit être combinée avec la 2ème option dans le cas où vous devez gérer une hiérarchie de classe comme par exemple une liste de membres qui est l'une des occurences de sous-classes d'une super classe de membres, par exemple Directeur étend membre ou étudiant étend membre. Dans ce cas, vous devez donner le type de sous-classe dans le format json

1
répondu Xavier Méhaut 2015-12-02 03:29:52

j'ai créé un outil qui génère des interfaces TypeScript et un" type map "runtime pour effectuer la vérification de type runtime contre les résultats de JSON.parse : ts.quicktype.io

par exemple, étant donné ce JSON:

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

quicktype produit l'interface typographique et la carte de type suivante:

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

ensuite, nous vérifions le résultat de JSON.parse contre la carte de type:

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

j'ai oublié un peu de code, mais vous pouvez essayer quicktype pour les détails.

1
répondu David Siegel 2017-09-09 01:14:11

peut-être pas réelle, mais solution simple:

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

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

travaillez aussi pour des dépendances difficiles!!!

0
répondu Михайло Пилип 2016-07-28 09:30:14

JQuery .étendre ce pour vous:

var mytsobject = new mytsobject();

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

$.extend(mytsobject, newObj); //mytsobject will now contain a & b
0
répondu Daniel 2016-08-12 14:55:10

une autre option utilisant les usines

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

utiliser comme ceci

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. permet de maintenir les classes simples
  2. injection disponible aux usines pour la flexibilité
0
répondu Anthony Johnston 2017-09-13 21:50:50

vous pouvez faire comme ci-dessous

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

et

var instance: Instance = <Instance>({
      id: null,
      name: '',
      type: ''
    });
-1
répondu Md Ayub Ali Sarker 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
répondu user8390810 2017-12-27 04:15:43