web-dev-qa-db-fra.com

Comment convertir un objet JSON en classe TypeScript

J'ai lu un objet JSON à partir d'un serveur distant REST. Cet objet JSON possède toutes les propriétés d'une classe TypeScript (par conception). Comment convertir l'objet JSON reçu en un type var?

Je ne veux pas remplir une variable TypeScript (c’est-à-dire avoir un constructeur qui accepte cet objet JSON). Il est volumineux et tout copier d'un sous-objet à l'autre et d'une propriété à l'autre prendrait beaucoup de temps.

Update: Vous pouvez cependant le convertir en interface TypeScript!

273
David Thielen

Vous ne pouvez pas convertir simplement un résultat JavaScript ancien-ancien d'une requête Ajax en une instance de classe prototype JavaScript/TypeScript. Il existe un certain nombre de techniques pour le faire, et impliquent généralement la copie de données. Sauf si vous créez une instance de la classe, elle n'aura aucune méthode ou propriété. Cela restera un simple objet JavaScript. 

Bien que si vous ne traitiez que des données, vous pouvez simplement transtyper une interface (comme il s’agit d’une structure de compilation), vous devrez utiliser une classe TypeScript qui utilise l’instance de données et effectue des opérations avec ces données.

Quelques exemples de copie des données:

  1. Copie d'un objet JSON AJAX dans un objet existant
  2. Analyse de la chaîne JSON dans un prototype d'objet particulier en JavaScript

En substance, vous voudriez simplement:

var d = new MyRichObject();
d.copyInto(jsonResult);
115
WiredPrairie

J'ai eu le même problème et j'ai trouvé une bibliothèque qui fait le travail: https://github.com/pleerock/class-transformer .

Cela fonctionne comme ceci: 

        let jsonObject = response.json() as Object;
        let fooInstance = plainToClass(Models.Foo, jsonObject);
        return fooInstance;

Il prend en charge les enfants imbriqués, mais vous devez décorer le membre de votre classe.

62
Pak

Dans TypeScript, vous pouvez faire une assertion type en utilisant une interface et des génériques comme ceci:

var json = Utilities.JSONLoader.loadFromFile("../docs/location_map.json");
var locations: Array<ILocationMap> = JSON.parse(json).location;

Où ILocationMap décrit la forme de vos données. L'avantage de cette méthode est que votre JSON peut contenir davantage de propriétés, mais que sa forme satisfait aux conditions de l'interface.

J'espère que ça aide!

40
user756310

J'ai trouvé un article très intéressant sur la conversion générique de JSON dans une classe TypeScript:

http://cloudmark.github.io/Json-Mapping/

Vous vous retrouvez avec le code suivant:

let example = {
                "name": "Mark", 
                "surname": "Galea", 
                "age": 30, 
                "address": {
                  "first-line": "Some where", 
                  "second-line": "Over Here",
                  "city": "In This City"
                }
              };

MapUtils.deserialize(Person, example);  // custom class
24
Philip Miglinci

Si vous utilisez ES6, essayez ceci:

class Client{
  name: string

  displayName(){
    console.log(this.name)
  }
}

service.getClientFromAPI().then(clientData => {

  // Here the client data from API only have the "name" field
  // If we want to use the Client class methods on this data object we need to:
  let clientWithType = Object.assign(new Client(), clientData)

  clientWithType.displayName()
})

Mais cette façon ne fonctionnera pas sur l'objet nid, malheureusement.

22
ilovezcd

En supposant que le json ait les mêmes propriétés que votre classe TypeScript, vous n'avez pas à copier vos propriétés Json dans votre objet TypeScript. Vous devrez simplement construire votre objet TypeScript en passant les données JSON dans le constructeur.

Dans votre rappel ajax, vous recevez une entreprise:

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

   // call the methods on your newCompany object ...
}

Afin de faire ce travail:

1) Ajoutez un constructeur dans votre classe TypeScript qui prend les données json en paramètre. Dans ce constructeur, vous étendez votre objet json avec jQuery, comme ceci: $.extend( this, jsonData). $ .extend permet de conserver les prototypes javascript tout en ajoutant les propriétés de l'objet json.

2) Notez que vous devrez faire la même chose pour les objets liés. Dans le cas de Employees dans l'exemple, vous créez également un constructeur prenant la partie des données JSON pour les employés. Vous appelez $ .map pour traduire les employés JSON en objets TypeScript Employee.

export class Company
{
    Employees : Employee[];

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

        if ( jsonData.Employees )
            this.Employees = $.map( jsonData.Employees , (emp) => {
                return new Employee ( emp );  });
    }
}

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

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

C’est la meilleure solution que j’ai trouvée en traitant des classes TypeScript et des objets json.

17

Dans mon cas ça marche. J'ai utilisé des fonctions Object.assign (cible, sources ...) . Tout d’abord, la création du bon objet, puis copie les données de l’objet json dans la cible. Exemple:

let u:User = new User();
Object.assign(u , jsonUsers);

Et un exemple d'utilisation plus avancé. Un exemple utilisant le tableau.

this.someService.getUsers().then((users: User[]) => {
  this.users = [];
  for (let i in users) {
    let u:User = new User();
    Object.assign(u , users[i]);
    this.users[i] = u;
    console.log("user:" + this.users[i].id);
    console.log("user id from function(test it work) :" + this.users[i].getId());
  }

});

export class User {
  id:number;
  name:string;
  fullname:string;
  email:string;

  public getId(){
    return this.id;
  }
}
13
Adam111p

TLDR: une doublure

// This assumes your constructor method will assign properties from the arg.
.map((instanceData: MyClass) => new MyClass(instanceData));

La réponse détaillée

Je not recommande l'approche Object.assign, car elle peut contenir de manière inappropriée votre instance de classe avec des propriétés non pertinentes (ainsi que des fermetures définies) qui n'ont pas été déclarées dans la classe.

Dans la classe dans laquelle vous essayez de désérialiser, je m'assure que toutes les propriétés que vous souhaitez désérialiser sont définies (null, tableau vide, etc.). En définissant vos propriétés avec des valeurs initiales, vous exposez leur visibilité lorsque vous tentez d'itérer les membres de la classe auxquels vous souhaitez affecter des valeurs (voir Méthode de désérialisation ci-dessous).

export class Person {
  public name: string = null;
  public favoriteSites: string[] = [];

  private age: number = null;
  private id: number = null;
  private active: boolean;

  constructor(instanceData?: Person) {
    if (instanceData) {
      this.deserialize(instanceData);
    }
  }

  private deserialize(instanceData: Person) {
    // Note this.active will not be listed in keys since it's declared, but not defined
    const keys = Object.keys(this);

    for (const key of keys) {
      if (instanceData.hasOwnProperty(key)) {
        this[key] = instanceData[key];
      }
    }
  }
}

Dans l'exemple ci-dessus, j'ai simplement créé une méthode de désérialisation. Dans un exemple concret, je le centraliserais dans une classe de base ou une méthode de service réutilisable.

Voici comment utiliser cela dans quelque chose comme un http resp ...

this.http.get(ENDPOINT_URL)
  .map(res => res.json())
  .map((resp: Person) => new Person(resp) ) );

Si tslint/ide se plaint du type d'argument incompatible, convertissez simplement l'argument dans le même type en utilisant des crochets angulaires <YourClassName>, par exemple:

const person = new Person(<Person> { name: 'John', age: 35, id: 1 });

Si vous avez des membres de classe qui sont d'un type spécifique (c'est-à-dire: instance d'une autre classe), vous pouvez les convertir en instances typées à l'aide de méthodes getter/setter.

export class Person {
  private _acct: UserAcct = null;
  private _tasks: Task[] = [];

  // ctor & deserialize methods...

  public get acct(): UserAcct {
    return this.acct;
  }
  public set acct(acctData: UserAcct) {
    this._acct = new UserAcct(acctData);
  }

  public get tasks(): Task[] {
    return this._tasks;
  }

  public set tasks(taskData: Task[]) {
    this._tasks = taskData.map(task => new Task(task));
  }
}

L'exemple ci-dessus désérialise à la fois acct et la liste des tâches dans leurs instances de classe respectives.

11
Timothy Perez

Il n'y a encore rien à vérifier automatiquement si l'objet JSON que vous avez reçu du serveur a les propriétés d'interface attendues (la lecture est conforme à celle de) de TypeScript. Mais vous pouvez utiliser Gardes de type défini par l'utilisateur

Considérant l'interface suivante et un objet JSON idiot (ça aurait pu être n'importe quel type):

interface MyInterface {
    key: string;
 }

const json: object = { "key": "value" }

Trois voies possibles:

A. Assertion de type ou transtypes statiques simples placés après la variable

const myObject: MyInterface = json as MyInterface;

B. Coulée statique simple, avant la variable et entre les diamants

const myObject: MyInterface = <MyInterface>json;

C. Distribution dynamique avancée, vous vérifiez vous-même la structure de l'objet

function isMyInterface(json: any): json is MyInterface {
    // silly condition to consider json as conform for MyInterface
    return typeof json.key === "string";
}

if (isMyInterface(json)) {
    console.log(json.key)
}
else {
        throw new Error(`Expected MyInterface, got '${json}'.`);
}

Vous pouvez jouer avec cet exemple ici

Notez que la difficulté ici est d’écrire la fonction isMyInterface. J'espère que TS ajoutera tôt ou tard un décorateur pour exportera le typage complexe vers le runtime et lui permettra de vérifier la structure de l'objet si nécessaire. Pour l'instant, vous pouvez utiliser un validateur de schéma json dont le but est approximativement le même OR ce générateur de fonction de vérification du type à l'exécution

6
Flavien Volken

Bien que ce ne soit pas un casting à proprement parler; J'ai trouvé https://github.com/JohnWhiteTB/TypedJSON une alternative utile. 

@JsonObject
class Person {
    @JsonMember
    firstName: string;

    @JsonMember
    lastName: string;

    public getFullname() {
        return this.firstName + " " + this.lastName;
    }
}
var person = TypedJSON.parse('{ "firstName": "John", "lastName": "Doe" }', Person);

person instanceof Person; // true
person.getFullname(); // "John Doe"
2
Neil

J'ai utilisé cette bibliothèque ici: https://github.com/pleerock/class-transformer

<script lang="ts">
    import { plainToClass } from 'class-transformer';
</script>

La mise en oeuvre:

private async getClassTypeValue() {
  const value = await plainToClass(ProductNewsItem, JSON.parse(response.data));
}

Parfois, vous devrez analyser les valeurs JSON de plainToClass pour comprendre qu’il s’agit d’une donnée formatée JSON.

1
Mac Chibueze

Une vieille question avec des réponses généralement correctes, mais pas très efficaces. C'est ce que je propose:

Créez une classe de base contenant la méthode init () et des méthodes de conversion statique (pour un seul objet et un tableau). Les méthodes statiques pourraient être n'importe où; la version avec la classe de base et init () permet des extensions faciles par la suite.

export class ContentItem {
    // parameters: doc - plain JS object, proto - class we want to cast to (subclass of ContentItem)
    static castAs<T extends ContentItem>(doc: T, proto: typeof ContentItem): T {
        // if we already have the correct class skip the cast
        if (doc instanceof proto) { return doc; }
        // create a new object (create), and copy over all properties (assign)
        const d: T = Object.create(proto.prototype);
        Object.assign(d, doc);
        // reason to extend the base class - we want to be able to call init() after cast
        d.init(); 
        return d;
    }
    // another method casts an array
    static castAllAs<T extends ContentItem>(docs: T[], proto: typeof ContentItem): T[] {
        return docs.map(d => ContentItem.castAs(d, proto));
    }
    init() { }
}

Des mécanismes similaires (avec assign () ) ont été mentionnés dans @ Adam111p post. Juste une autre façon (plus complète) de le faire. @ Timothy Perez est critique de assign () , mais à mon avis, il est tout à fait approprié ici.

Implémentez une classe dérivée (la vraie):

import { ContentItem } from './content-item';

export class SubjectArea extends ContentItem {
    id: number;
    title: string;
    areas: SubjectArea[]; // contains embedded objects
    depth: number;

    // method will be unavailable unless we use cast
    lead(): string {
        return '. '.repeat(this.depth);
    }

    // in case we have embedded objects, call cast on them here
    init() {
        if (this.areas) {
            this.areas = ContentItem.castAllAs(this.areas, SubjectArea);
        }
    }
}

Nous pouvons maintenant lancer un objet extrait du service:

const area = ContentItem.castAs<SubjectArea>(docFromREST, SubjectArea);

Toutes les hiérarchies de SubjectArea objects auront une classe correcte.

Un cas d'utilisation/exemple; créer un service angulaire (classe de base abstraite à nouveau):

export abstract class BaseService<T extends ContentItem> {
  BASE_URL = 'http://Host:port/';
  protected abstract http: Http;
  abstract path: string;
  abstract subClass: typeof ContentItem;

  cast(source: T): T {
    return ContentItem.castAs(source, this.subClass);
  }
  castAll(source: T[]): T[] {
    return ContentItem.castAllAs(source, this.subClass);
  }

  constructor() { }

  get(): Promise<T[]> {
    const value = this.http.get(`${this.BASE_URL}${this.path}`)
      .toPromise()
      .then(response => {
        const items: T[] = this.castAll(response.json());
        return items;
      });
    return value;
  }
}

L'utilisation devient très simple. créer un service de zone:

@Injectable()
export class SubjectAreaService extends BaseService<SubjectArea> {
  path = 'area';
  subClass = SubjectArea;

  constructor(protected http: Http) { super(); }
}

get () La méthode du service retournera la promesse d'un tableau déjà converti en SubjectArea objects (toute la hiérarchie)

Maintenant, disons, nous avons une autre classe: 

export class OtherItem extends ContentItem {...}

Créer un service qui récupère les données et les convertit dans la classe correcte est aussi simple que:

@Injectable()
export class OtherItemService extends BaseService<OtherItem> {
  path = 'other';
  subClass = OtherItem;

  constructor(protected http: Http) { super(); }
}
1

J'ai créé un outil simple pour ce faire https://beshanoe.github.io/json2ts/ Contrairement à json2ts.com, il fonctionne directement dans un navigateur et n'envoie pas vos données à des serveurs inconnus. En outre, il a plusieurs paramètres. Je vais travailler pour améliorer sa fonctionnalité

1
beshanoe

Dans les derniers TS, vous pouvez faire comme ceci:

const isMyInterface = (val: any): val is MyInterface => {
  if (!val) { return false; }
  if (!val.myProp) { return false; }
  return true;
};

Et que l'utilisateur aime ceci:

if (isMyInterface(data)) {
 // now data will be type of MyInterface
}
0
Jaroslav

J'ai rencontré un besoin similaire. Je voulais quelque chose qui me facilitera la transformation de/vers JSON qui provient d'un appel REST api vers/depuis une définition de classe spécifique. Les solutions que j'ai trouvées étaient insuffisantes ou destinées à réécrire le code de mes classes et à ajouter des annotations ou des éléments similaires.

Je voulais que GSON soit utilisé dans Java pour sérialiser/désérialiser les classes vers/à partir d'objets JSON.

Combiné à un besoin ultérieur, à savoir que le convertisseur fonctionne également dans JS, j'ai fini d'écrire mon propre paquet.

Il a cependant, un peu de frais généraux. Mais une fois démarré, il est très pratique d’ajouter et d’éditer.

Vous initialisez le module avec:

  1. schéma de conversion - permet de mapper entre les champs et de déterminer comment la conversion sera effectuée
  2. Classes map array
  3. Carte des fonctions de conversion - pour les conversions spéciales.

Ensuite, dans votre code, vous utilisez le module initialisé comme:

const convertedNewClassesArray : MyClass[] = this.converter.convert<MyClass>(jsonObjArray, 'MyClass');

const convertedNewClass : MyClass = this.converter.convertOneObject<MyClass>(jsonObj, 'MyClass');

ou, à JSON:

const jsonObject = this.converter.convertToJson(myClassInstance);

Utilisez ce lien vers le paquet npm et une explication détaillée sur la façon de travailler avec le module: json-class-converter

Également emballé pour
Utilisation angulaire dans: convertisseur angulaire-classe-json

0
Doronm

Parce que nous sommes une boutique .NET, nous avons créé cet outil ( https://github.com/Centeva/TypeScripter ).

Il construit des classes TypeScript à partir de nos DLL. Tout ce que nous avons à faire est d’introduire notre réponse dans la classe en tant que param. Cela fonctionne bien pour nous.

0
Devcon

Je ne vois aucune mention de json-TypeScript-mapper: https://www.npmjs.com/package/json-TypeScript-mapper . Autant que je sache, cela ressemble à une combinaison de la découverte de @ PhilipMiglinci et de la réponse de @ Pak.

0
BBaysinger

J'utilise Angular 6 sur le frontend et l'application Spring Boot sur le backend qui renvoie Java Objects. Tout ce que je dois faire est de définir une classe similaire sur l’application angular qui possède des propriétés correspondantes, puis accepter l’objet 'en tant que' un 'objet angular class (comp comme société dans l'exemple ci-dessous).

Reportez-vous au code frontal ci-dessous par exemple. Faites-moi savoir dans les commentaires si quelque chose nécessite plus de clarté.

  createCompany() {
    let company = new Company();
    company.name = this.companyName;

    this.companyService.createCompany(company).subscribe(comp => {
       if (comp !== null) {
        this.employerAdminCompany = comp as Company;
      }
    });
  }

où company est un objet entité dans l'application de démarrage de printemps et constitue également une classe dans Angular.

export class Company {
    public id: number;
    public name: string;
    public owner: User;
    public locations: Array<Location>;
}
0
Maneesh Parihar

Vous pouvez créer une interface de votre type (SomeType) et convertir l'objet en cela.

const typedObject: SomeType = <SomeType> responseObject;
0
Jayant Varshney