Comment initialiser un objet TypeScript avec un objet JSON

198

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

Quelle est la meilleure façon de l'initialiser? Je ne pense pas que cela fonctionnera parce que la classe (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 recherche les noms des membres et les attribue à travers, créant des listes et instanciant les classes selon les besoins, donc je n'ai pas à écrire de code explicite pour chaque membre de chaque classe (il y a beaucoup!)

David Thielen
la source
1
Pourquoi avez-vous demandé à nouveau cela (comme la réponse que j'ai fournie dans l'autre question a dit que cela ne fonctionnerait pas et qu'il s'agissait de copier des propriétés dans un objet existant)?
WiredPrairie
1
doublon possible de Comment
convertir
3
@WiredPrairie cette question est différente, elle demande si je peux parcourir les propriétés une par une et les assigner. Les autres questions demandaient si je pouvais le lancer.
David Thielen
1
@WiredPrairie cont: Si vous continuez à plonger dans les propriétés jusqu'à ce que vous arriviez aux types primitifs, ceux-ci peuvent être attribués à travers.
David Thielen
2
Il copie toujours toutes les valeurs comme je l'ai suggéré. Il n'y a pas de nouveau moyen de le faire dans TypeScript car c'est une conception fondamentale de JavaScript. Pour les gros objets, vous ne voudrez peut-être pas copier de valeurs et simplement "agir sur" la structure de données à la place.
WiredPrairie du

Réponses:

188

Voici quelques plans rapides pour montrer quelques façons différentes. Ils ne sont en aucun cas "complets" et en tant que déni de responsabilité, je ne pense pas que ce soit une bonne idée de le faire comme ça. De plus, le code n'est pas trop propre car je viens de le taper assez rapidement.

Également comme note: Bien sûr, les classes désérialisables doivent avoir des constructeurs par défaut comme c'est le cas dans tous les autres langages où je suis au courant de la désérialisation de toute sorte. Bien sûr, Javascript ne se plaindra pas si vous appelez un constructeur non par défaut sans arguments, mais la classe devrait alors y être préparée (en plus, ce ne serait pas vraiment la "méthode de la typographie").

Option n ° 1: aucune information d'exécution

Le problème avec cette approche est principalement que le nom d'un 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 cela, mais inscrivez-le simplement ici parce que c'était le premier "brouillon" quand j'ai écrit cette réponse (c'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: la propriété name

Pour se débarrasser du problème dans l'option # 1, nous devons avoir une sorte d'informations sur le type d'un nœud dans l'objet JSON. Le problème est que dans Typescript, ces choses sont des constructions au moment de la compilation et nous en avons besoin lors de l'exécution - mais les objets d'exécution n'ont tout simplement pas conscience de leurs propriétés jusqu'à ce qu'ils soient définis.

Une façon de le faire consiste à informer les classes de leurs noms. Vous avez également besoin de cette propriété dans le JSON. En fait, vous n'en avez besoin que 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 n ° 3: indiquer explicitement les types de membres

Comme indiqué ci-dessus, les informations de type des membres de la classe ne sont pas disponibles au moment de l'exécution, c'est-à-dire à moins que nous ne les rendions disponibles. Nous n'avons besoin de faire cela que pour les membres non primitifs et nous sommes prêts à partir:

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: La manière verbeuse, mais soignée

Mise à jour du 01/03/2016: Comme @GameAlchemist l'a souligné dans les commentaires ( idée , implémentation ), à partir de Typescript 1.7, la solution décrite ci-dessous peut être mieux écrite à l'aide de décorateurs de classe / propriété.

La sérialisation est toujours un problème et à mon avis, le meilleur moyen est un moyen qui n'est tout simplement pas le plus court. De toutes les options, c'est ce que je préférerais parce que l'auteur de la classe a un 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, vous causeront des ennuis (à moins que Javascript ne propose une méthode native pour y faire face).

Vraiment, l'exemple suivant ne rend pas justice à la flexibilité. Il ne fait 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 pour utiliser tout type de JSON qu'elle souhaite contrôler l'état de la classe entière (vous pouvez 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);
Ingo Bürk
la source
12
L'option n ° 4 est ce que j'appellerais une façon raisonnable de procéder. Vous devez toujours écrire le code de désérialisation, mais il est dans la même classe et entièrement contrôlable. Si vous venez de Java, cela est comparable à l'écriture equalsou aux toStringméthodes (seulement que vous les avez généralement générées automatiquement). Il ne devrait pas être trop difficile d'écrire un générateur deserializesi vous le souhaitez, mais il ne peut tout simplement pas s'agir d'une automatisation d'exécution.
Ingo Bürk
2
@ IngoBürk, je sais que je pose cette question 2 ans plus tard, mais comment cela fonctionnera-t-il sur un ensemble d'objets? L'exemple de code ci-dessus fonctionne correctement pour l'objet JSON. comment peut-il être utilisé pour un tableau d'objets?
Pratik Gaikwad
2
Une remarque secondaire: depuis la 1.7, (certes plus récente que votre réponse), tapuscrit fournit des décorateurs de classe / propriété qui permettent d'écrire la 4ème solution de manière plus nette.
GameAlchemist
1
La meilleure documentation que j'ai trouvée est une réponse StackOverflow: stackoverflow.com/a/29837695/856501 . J'ai utilisé des décorateurs dans un de mes projets, et bien que j'aimerais quelques autres fonctionnalités, je dois dire qu'elles fonctionnent comme un charme.
GameAlchemist
2
Je ne voudrais pas sauter sur les décorateurs pour un projet de production pour l'instant - gardez à l'esprit qu'ils sont toujours une fonctionnalité expérimentale. Je ne baserais pas le code du monde réel sur des "expériences", car en ce qui nous concerne, elles pourraient disparaître dans la prochaine version et vous auriez à réécrire un tas de code ou être à jamais coincé sur une ancienne version TS. Just my $ .02
RVP
35

vous pouvez utiliser Object.assignJe 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 );

voici 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 dit chrome

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

de sorte que vous pouvez voir qu'il ne fait pas l'attribution récursivement

xénoterracide
la source
2
donc, au fond, il est ceci: Object.assign. Pourquoi avons-nous alors deux réponses de type lexique au-dessus de celle-ci?
phil294
18
@Blauhim Parce Object.assignque ne fonctionnera pas récursivement et n'instanciera pas les types d'objets corrects, en laissant les valeurs comme Objectinstances. Bien qu'il convienne aux tâches triviales, la sérialisation de type complexe n'est pas possible avec. Par exemple, si une propriété de classe est d'un type de classe personnalisé, JSON.parse+ Object.assigninstanciera cette propriété en Object. Les effets secondaires incluent les méthodes et les accessoires manquants.
John Weisz
@JohnWeisz la classe de niveau supérieur d'attribution d'objet a le type correct, et j'ai mentionné la chose récursive dans cela ... cela dit, YMMV, et ceux-ci pourraient être des rupteurs d'affaire.
xenoterracide
Cité directement à partir de la question: "la classe 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 [...] Je préférerais une approche qui recherche le membre les noms et les attribue à travers, en créant des listes et des classes d'instanciation selon les besoins, donc je n'ai pas à écrire de code explicite pour chaque membre de chaque classe " - ce qui n'est pas le cas Object.assign, où il s'agit toujours d'écrire une instanciation imbriquée par main. Cette approche convient parfaitement aux objets très simples de niveau didacticiel, mais pas à une utilisation réelle.
John Weisz
@JohnWeisz a répondu, la plupart du temps, car il ne figurait dans aucune réponse et semblait simple pour certains cas d'utilisation. Je suis certain qu'il pourrait également être utilisé en combinaison avec d'autres réponses telles que la réflexion, afin de faire ce que vous recherchez. Je l'ai également écrit en partie pour que je m'en souvienne plus tard. En regardant ces réponses et en ayant utilisé et écrit des bibliothèques beaucoup plus puissantes, il ne semble rien être disponible pour une «utilisation réelle».
xenoterracide
34

TLDR: TypedJSON (preuve de concept de travail)


La racine de la complexité de ce problème est que nous devons désérialiser JSON à l' exécution en utilisant des informations de type qui n'existent qu'au moment de la compilation . Cela nécessite que les informations de type soient en quelque sorte mises à disposition au moment de l'exécution.

Heureusement, cela peut être résolu de manière très élégante et robuste avec les décorateurs et ReflectDecorators :

  1. Utilisez des décorateurs de propriétés sur les propriétés soumises à la sérialisation, pour enregistrer les informations de métadonnées et stocker ces informations quelque part, par exemple sur le prototype de classe
  2. Alimentez ces informations de métadonnées dans un initialiseur récursif (désérialiseur)

 

Enregistrement des informations de type

Avec une combinaison de ReflectDecorators et de décorateurs de propriétés, les informations de type peuvent être facilement enregistrées 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é donnée, l'extrait ci-dessus ajoutera une référence de la fonction constructeur de la propriété à la __propertyTypes__propriété cachée du prototype de 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 lors de l'exécution, qui peuvent maintenant être traitées.

 

Traitement des informations de type

Nous devons d'abord obtenir une Objectinstance en utilisant JSON.parse- après cela, nous pouvons parcourir les entrées __propertyTypes__(collectées ci-dessus) et instancier les propriétés requises en conséquence. Le type de l'objet racine doit être spécifié pour que le désérialiseur ait un point de départ.

Encore une fois, une simple mise en œuvre 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 présente un grand avantage de désérialisation par les types attendus (pour les valeurs complexes / objet), au lieu de ce qui est présent dans le JSON. Si un Personest attendu, c'est une Personinstance qui est créée. Avec quelques mesures de sécurité supplémentaires en place pour les types et les tableaux primitifs, cette approche peut être sécurisée, qui résiste à tout JSON malveillant.

 

Étuis Edge

Cependant, si vous êtes maintenant heureux que la solution soit aussi simple, j'ai une mauvaise nouvelle: il y a un grand nombre de cas marginaux qui doivent être pris en charge. Seuls certains d'entre eux sont:

  • Tableaux et éléments de tableau (en particulier 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 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 toujours considérés comme expérimentaux, je ne recommanderais pas de l'utiliser pour la production, mais jusqu'à présent, cela m'a bien servi.

John Weisz
la source
TypedJSON a très bien fonctionné; merci beaucoup pour la référence.
Neil
Excellent travail, vous avez trouvé une solution très élégante à un problème qui me préoccupe depuis un certain temps. Je suivrai votre projet de très près!
John Strickler
12

J'utilise ce type pour faire le travail: https://github.com/weichx/cerialize

C'est très simple mais puissant. Elle supporte:

  • Sérialisation et désérialisation de tout un arbre d'objets.
  • Propriétés persistantes et transitoires sur le même objet.
  • Crochets pour personnaliser la logique de (dé) sérialisation.
  • Il peut (dé) sérialiser en une instance existante (idéal pour Angular) 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);
André
la source
6

J'ai créé un outil qui génère des interfaces TypeScript et une "mappe de types" d'exécution pour effectuer une vérification typographique d'exécution par rapport aux 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 TypeScript et la carte de types suivantes:

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.parsela carte de type:

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

J'ai omis du code, mais vous pouvez essayer de saisir rapidement les détails.

David Siegel
la source
1
Après avoir fait de nombreuses heures de recherche et essayé quelques techniques d'analyse, je peux dire que c'est une excellente solution - principalement parce que les décorateurs sont encore expérimentaux. * Le lien d'origine est rompu pour moi; mais ts.quicktype.io fonctionne. * La conversion du schéma JSON en schéma JSON est une bonne première étape.
LexieHankins
3

Option n ° 5: utilisation des constructeurs Typescript et jQuery.extend

Cela semble être la méthode la plus maintenable: ajoutez un constructeur qui prend en paramètre la structure json et étendez 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 répertorier les 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 rappel ajax où vous recevez une entreprise pour calculer les salaires:

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

   // call the methods on your newCompany object ...
   newCompany.calculateSalaries()
}
Anthony Brenelière
la source
d'où $.extendviens-tu?
whale_steward
@whale_steward Je suppose que l'auteur fait référence à la bibliothèque jQuery. Dans le monde JavaScript, «$» est très souvent une personne utilisant jQuery.
Nick Roth
comment l'importer? il suffit de l'inclure sur la tête html est suffisant?
whale_steward
oui je mets à jour la réponse pour remplacer $ par jQuery. importez jQuery.js dans la tête html, et installez et ajoutez @ types / jquery dans votre package.json, section devDependencies.
Anthony Brenelière
1
Notez qu'en Javascript, vous devriez le faire Object.assign, ce qui supprime cette dépendance à jQuery.
Léon Pelletier
2

Pour les objets simples, j'aime cette méthode:

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

Tirer parti de la capacité de définir des propriétés dans le constructeur permet d'être concis.

Cela vous donne un objet typé (par rapport à toutes les réponses qui utilisent Object.assign ou une variante, qui vous donne un objet) et ne nécessite pas de bibliothèques ou de décorateurs externes.

stevex
la source
1

La 4ème option décrite ci-dessus est une façon 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 classes comme par exemple une liste de membres qui est l'une des occurrences de sous-classes de une super classe de membre, par exemple le directeur étend le membre ou l'étudiant étend le membre. Dans ce cas, vous devez donner le type de sous-classe au format json

Xavier Méhaut
la source
1

JQuery .extend fait ceci pour vous:

var mytsobject = new mytsobject();

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

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

le meilleur que j'ai trouvé à cet effet est le transformateur de classe. github.com/typestack/class-transformer

Voilà comment vous l'utilisez:

Certains cours:

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

Si vous utilisez le décorateur @Type, les propriétés imbriquées seront également créées.

Fabianus
la source
0

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

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

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

fonctionne aussi pour les dépendances difficiles !!!

Михайло Пилип
la source
9
Cette approche ne fonctionne pas réellement comme prévu. Si vous inspectez les résultats d'exécution, bazils seront de type Objectet non de type. Bar.Cela fonctionne dans ce cas simple car Barn'a pas de méthodes (juste des propriétés primitives). Si Barune méthode similaire existait isEnabled(), cette approche échouerait car cette méthode ne serait pas dans la chaîne JSON sérialisée.
Todd
0

Une autre option en utilisant des 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 ça

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. garde vos cours simples
  2. injection disponible aux usines pour plus de flexibilité
Anthony Johnston
la source
0

Personnellement, je préfère l'option n ° 3 de @Ingo Bürk. Et j'ai amélioré ses codes pour prendre en charge un tableau de données complexes et un tableau de données primitives.

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
alireza etemadi
la source
-1

vous pouvez faire comme ci-dessous

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

et

var instance: Instance = <Instance>({
      id: null,
      name: '',
      type: ''
    });
Md Ayub Ali Sarker
la source
Cela ne produira pas réellement une instance d'exécution de votre type d'objet attendu. Il semblera fonctionner lorsque votre type n'a que des propriétés primitives, mais échouera lorsqu'un type a des méthodes. Les définitions d'interface ne sont pas non plus disponibles au moment de l'exécution (uniquement au moment de la construction).
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;
    }
}
user8390810
la source
appeler le contenu comme exemple ci-dessous:
user8390810
<a class="navbar-brand" href="#"> {{keyItems.key.logo}} </a>
user8390810
Cela ne semble pas "[instancier] les classes selon les besoins".
LexieHankins