Jak rzutować obiekt JSON na klasę maszynopisu

393

Czytałem obiekt JSON ze zdalnego serwera REST. Ten obiekt JSON ma wszystkie właściwości klasy maszynopisu (według projektu). Jak rzutować otrzymany obiekt JSON na typ var?

Nie chcę wypełniać var ​​maszynopisu (tj. Mieć konstruktor, który pobiera ten obiekt JSON). Jest duży, a kopiowanie wszystkiego między podobiektami według podobiektów i właściwości według właściwości zajęłoby dużo czasu.

Aktualizacja: Możesz jednak rzucić go na interfejs maszynopisu!

David Thielen
źródło
możesz użyć github.com/vojtechhabarta/typescript-generator do generowania interfejsów TypeScript w przypadku, gdy JSON jest mapowany przy użyciu klas Java
Vojta
Zakodowałem małą bibliotekę castingową: sulphur-blog.azurewebsites.net/typescript-mini-cast-library
Camille Wintz
1
Obiekt JSON = obiekt JavaScript Object Notation. Nie ma tam wystarczająco dużo przedmiotów, mówię, że dla pewności wrzucimy jeszcze kilka.
bug-a-lot
1
Zrobiłem narzędzie do tego beshanoe.github.io/json2ts
beshanoe
Utworzenie prototypowej klasy TypeScript do zdefiniowania obiektu nie zaszkodzi prawdziwemu kodowi produkcyjnemu. Spójrz na skompilowany plik JS, wszystkie definicje zostaną usunięte, ponieważ nie są częścią JS.
FisNaN

Odpowiedzi:

167

Nie można w prosty sposób rzutować wyniku zwykłego starego kodu JavaScript z żądania Ajax na prototypową instancję klasy JavaScript / TypeScript. Można to zrobić na wiele sposobów i zazwyczaj wiąże się to z kopiowaniem danych. O ile nie utworzysz instancji klasy, nie będzie ona miała żadnych metod ani właściwości. Pozostanie prostym obiektem JavaScript.

Chociaż jeśli masz do czynienia tylko z danymi, możesz po prostu wykonać rzutowanie do interfejsu (ponieważ jest to wyłącznie struktura czasowa kompilacji), wymagałoby to użycia klasy TypeScript, która korzysta z instancji danych i wykonuje operacje na tych danych.

Kilka przykładów kopiowania danych:

  1. Kopiowanie obiektu AJAX JSON do istniejącego obiektu
  2. Parsuj ciąg JSON w prototypie określonego obiektu w JavaScript

Zasadniczo po prostu:

var d = new MyRichObject();
d.copyInto(jsonResult);
WiredPrairie
źródło
Zgadzam się z twoją odpowiedzią. Jako dodatek, chociaż nie jestem w stanie teraz tego sprawdzić i przetestować, myślę, że te dwa kroki można połączyć, dając funkcję budzenia jako parametr JSON.parse(). Oba nadal będą musiały zostać wykonane, ale pod względem składniowym można je połączyć.
JAAulde
Pewnie, to też może zadziałać - nie mam pojęcia, czy byłoby to bardziej wydajne, ponieważ musiałoby wywołać dodatkowe wywołanie funkcji dla każdej właściwości.
WiredPrairie
? Zdecydowanie nie odpowiedź szukałem :( Z ciekawości dlaczego ten Wydaje mi się sposób, w jaki działa javascript, że powinno to być wykonalne.
David THIELEN
Nie, to nie działa w TypeScript, ponieważ nie ma na to łatwego sposobu w JavaScript.
WiredPrairie
1
Co powiesz naObject.setPrototypeOf
Petah
102

Miałem ten sam problem i znalazłem bibliotekę wykonującą to zadanie: https://github.com/pleerock/class-transformer .

Działa to tak:

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

Obsługuje zagnieżdżone dzieci, ale musisz udekorować członka swojej klasy.

Pak
źródło
14
Ta genialna mała biblioteka rozwiązała ją idealnie przy najmniejszym wysiłku ( @Typechoć nie zapomnij o swoich adnotacjach). Ta odpowiedź zasługuje na większe uznanie.
Benny Bottema
O wow !, ta biblioteka nie jest tak mała, że ​​może ma wszystko, czego potrzebujesz, a nawet pozwala kontrolować transformację za pomocą dekoratora @transform: D
Diego Fernando Murillo Valenci
3
Zauważ, że ta biblioteka jest ledwo utrzymywana. To nie działa już z Angular5 +, a ponieważ nie łączą już nawet żądań ściągania, nie sądzę, że będą nad tym wkrótce pracować. To świetna biblioteka.
kentor
Istnieje obejście dla Angular5 + (to właściwie Angular Bug): github.com/typestack/class-transformer/issues/108
Pak
2
Działa to dobrze w Angular 6 (przynajmniej w moim przypadku użycia, który jest po prostu dosłownie odwzorować klasę JSON <=>)
tftd
54

W TypeScript można wykonać asercję typu za pomocą interfejsu i ogólnych:

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

Gdzie ILocationMap opisuje kształt twoich danych. Zaletą tej metody jest to, że JSON może zawierać więcej właściwości, ale kształt spełnia warunki interfejsu.

Mam nadzieję że to pomogło!

użytkownik756310
źródło
49
FYI: To stwierdzenie typu, a nie obsada.
WiredPrairie
6
Zobacz tutaj różnicę między asercją typu i rzutowaniem .
Stefan Hanke
7
Gdzie mogę znaleźć Utilities.JSONLoader?
HypeXR
22
Ale nie będzie miał żadnych metod, jak wspomniano w odpowiedzi.
Martín Coll
1
@StefanHanke wygląda, jakby adres URL się nieznacznie zmienił: „Typ asercji vs. casting”
ruffin
37

Jeśli używasz ES6, spróbuj tego:

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

Niestety ten sposób nie będzie działał na obiekcie gniazdowym .

migcoder
źródło
4
Poprosili o to w maszynopisie.
joe.feser
HI @ joe.feser, wspominam ES6, ponieważ w ten sposób wymagana jest metoda „Object.assign”.
migcoder,
1
W przypadku braku domyślnego konstruktora można utworzyć instancję docelową Object.create(MyClass.prototype), pomijając konstruktor.
Marcello
Więcej wyjaśnień na temat ograniczenia zagnieżdżania obiektów można znaleźć na stackoverflow.com/questions/22885995/...
Michael Freidgeim
28

Znalazłem bardzo interesujący artykuł na temat ogólnego rzutowania JSON na klasę maszynopisu:

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

Otrzymasz następujący kod:

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
Philip
źródło
20

TLDR: Jedna wkładka

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

Szczegółowa odpowiedź

Nie zrobiłbym tego polecam Object.assign podejście, ponieważ może niewłaściwie miot czy instancja klasy o właściwościach nieistotnych (jak zdefiniowano zamknięcia), które nie zostały zgłoszone w ramach samej klasie.

W klasie, w której próbujesz deserializować, upewniłbym się, że zdefiniowane są wszystkie właściwości, które chcesz deserializować (null, pusta tablica itp.). Definiując swoje właściwości za pomocą wartości początkowych, ujawniasz ich widoczność podczas próby iteracji członków klasy w celu przypisania wartości (patrz metoda deserializacji poniżej).

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

W powyższym przykładzie po prostu utworzyłem metodę deserializacji. W prawdziwym przykładzie chciałbym, aby był scentralizowany w klasie bazowej lub metodzie usługi wielokrotnego użytku.

Oto, jak wykorzystać to w czymś takim jak http resp ...

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

Jeśli tslint / ide skarży się na niezgodność typu argumentu, wystarczy rzutować argument na ten sam typ za pomocą nawiasów kątowych <YourClassName>, przykład:

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

Jeśli masz członków klasy, które są określonego typu (aka: wystąpienie innej klasy), możesz ich rzutować na wpisane wystąpienia za pomocą metod 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));
  }
}

Powyższy przykład deserializuje zarówno acct, jak i listę zadań do odpowiednich instancji klas.

Timothy Perez
źródło
Pojawia się następujący komunikat o błędzie: Wpisz „{name: string, age: number, id: number}” nie można przekonwertować na typ „Person”. Właściwość „id” jest prywatna w typie „Person”, ale nie w typie „{name: string, age: number, id: number}”
utiq
Jak powinienem tego używać z wyliczeniami? Czy muszę używać określonego typu podejścia i dodawać do niego getter i setter?
Tadija Bagarić
@TimothyParez Kiedy ustawiasz zadania?
Kay
Próbowałem zrobić coś podobnego, ale moja tablica zadań jest pusta, gdy konsola osoba.log.
Kay
Aby to skompilować, musiałem dodać Sygnaturę indeksu do klasy: klasa eksportu Osoba {[klucz: ciąg]: dowolny (...)}
Asimov
18

Zakładając, że json ma takie same właściwości jak klasa maszynopisu, nie musisz kopiować właściwości Json do obiektu maszynopisu. Musisz tylko zbudować obiekt Typescript przekazując dane json w konstruktorze.

W wywołaniu zwrotnym ajax otrzymujesz firmę:

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

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

Aby to zadziałało:

1) Dodaj konstruktor do klasy Typescript, który przyjmuje dane json jako parametr. W tym konstruktora przedłużyć swój obiekt json z jQuery, jak ten: $.extend( this, jsonData). $ .extend pozwala zachować prototypy javascript podczas dodawania właściwości obiektu json.

2) Pamiętaj, że musisz zrobić to samo dla połączonych obiektów. W przypadku pracowników w tym przykładzie tworzony jest również konstruktor, który pobiera część danych json dla pracowników. Wywołujesz $ .map, aby tłumaczyć pracowników json na maszynopis obiektów pracowników.

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

To najlepsze rozwiązanie, jakie znalazłem podczas pracy z klasami Typescript i obiektami json.

Anthony Brenelière
źródło
Wolę to rozwiązanie niż wdrażanie i utrzymywanie interfejsów, ponieważ moje aplikacje Angular2 mają prawdziwy model aplikacji, który może różnić się od modelu usług internetowych używanych przez moją aplikację. Może mieć prywatne dane i obliczone właściwości.
Anthony Brenelière,
7
Korzystanie z JQuery w projektach Angular to okropny pomysł. A jeśli twoje modele zawierają wiele funkcji, nie są już modelami.
Davor
1
@Davor Masz na myśli POJO lub model? POJO (w zasadzie zwykłe obiekty) nie mają żadnych funkcji, podczas gdy model jest szerszym pojęciem i obejmuje repozytorium. Wzorzec repozytorium, w przeciwieństwie do POJO, dotyczy funkcji, ale nadal jest modelem.
forsberg,
@Davor: używanie JQuery w projektach Angular nie jest złym pomysłem, dopóki nie używasz go do manipulowania DOM, co jest naprawdę okropnym pomysłem. Korzystam z dowolnej biblioteki potrzebnej do moich projektów Angular, a dla jQuery nie jest to opcja, ponieważ mój projekt używa SignalR, który jest od niej zależny. W przypadku klas, obecnie używanych przez javascript ES6, dostęp do danych uzyskuje się za pomocą właściwości, które są funkcją, która określa sposób przechowywania danych w pamięci. Dla konstruktorów istnieje właściwy sposób korzystania z fabryk.
Anthony Brenelière
OP dotyczy wyraźnie zwykłych modeli danych dla REST. Sprawiasz, że jest to niepotrzebnie skomplikowane, chłopaki. I tak, możesz użyć Jquery do dodatkowych rzeczy, ale importujesz ogromną bibliotekę, aby użyć 1%. To zapach kodu, jeśli go kiedykolwiek widziałem.
Davor,
18

Nie ma jeszcze nic, aby automatycznie sprawdzić, czy obiekt JSON otrzymany z serwera ma oczekiwane (zgodne z) właściwości interfejsu maszynopisu. Możesz jednak użyć zdefiniowanych przez użytkownika osłon typu

Biorąc pod uwagę następujący interfejs i głupiutki obiekt Json (mógł to być dowolny typ):

interface MyInterface {
    key: string;
 }

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

Trzy możliwe sposoby:

A. Typ Asercja lub zwykły rzut statyczny umieszczony za zmienną

const myObject: MyInterface = json as MyInterface;

B. Proste odlewanie statyczne, przed zmienną i między diamentami

const myObject: MyInterface = <MyInterface>json;

C. Zaawansowany rzut dynamiczny, sam sprawdzasz strukturę obiektu

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

Możesz grać z tym przykładem tutaj

Zauważ, że trudność polega na napisaniu isMyInterfacefunkcji. Mam nadzieję, że TS prędzej czy później doda dekorator, aby wyeksportować złożone pisanie do środowiska wykonawczego i pozwolić środowisku wykonawczemu sprawdzić strukturę obiektu w razie potrzeby. Na razie możesz użyć walidatora schematu json, którego przeznaczenie jest w przybliżeniu takie samo LUB tego generatora funkcji sprawdzania typu środowiska wykonawczego

Flavien Volken
źródło
1
Myślę, że twoja odpowiedź powinna być na górze
afzalex
Zdecydowanie. To najlepsza odpowiedź, która opiera się bezpośrednio na stronie internetowej ts.
Burak Karakuş
15

W moim przypadku to działa. Użyłem funkcji Object.assign (target, źródła ...) . Najpierw utworzenie poprawnego obiektu, a następnie skopiowanie danych z obiektu json do obiektu docelowego. Przykład:

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

I bardziej zaawansowany przykład użycia. Przykład z wykorzystaniem tablicy.

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;
  }
}
Adam111p
źródło
Co dzieje się, gdy masz prywatną własność?
prasanthv
Ponieważ obiekt jsonUser nie jest klasą użytkownika. Bez operacji Object.assign (u, jsonUsers); Nie można użyć funkcji getId (). Dopiero po przypisaniu otrzymasz prawidłowy obiekt użytkownika, w którym możesz użyć funkcji getId (). Funkcja getId () służy tylko do tego, że operacja się powiodła.
Adam111p
możesz pominąć temp var - po prostu zróbthis.users[i] = new User(); Object.assign(this.users[i], users[i])
cyptus
lub jeszcze lepiej skorzystać z wartości zwracanej:this.users[i] = Object.assign(new User(), users[i]);
cyptus
Ta długa wersja służy wyłącznie jako wyjaśnienie. Możesz skrócić kod tak, jak chcesz :)
Adam111p
6

Chociaż samo w sobie nie rzuca; Znalazłem https://github.com/JohnWhiteTB/TypedJSON jako przydatną alternatywę.

@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"
Neil
źródło
1
To nie jest casting, co to naprawdę robi?
DanielM
1
To rozwiązanie wymaga ogromnej ilości adnotacji. Czy naprawdę nie ma prostszego sposobu?
JPNotADragon
3

Możesz utworzyć interfaceswój typ ( SomeType) i rzucić w niego obiekt.

const typedObject: SomeType = <SomeType> responseObject;
Jayant Varshney
źródło
3

Jeśli chcesz rzutować obiekt json na klasę maszynopisu i mieć dostępne metody instancji w obiekcie wynikowym, którego musisz użyć Object.setPrototypeOf, tak jak zrobiłem to w poniższym fragmencie kodu:

Object.setPrototypeOf(jsonObject, YourTypescriptClass.prototype)
s_bighead
źródło
2

Stare pytanie z najczęściej poprawnymi, ale niezbyt skutecznymi odpowiedziami. Oto co proponuję:

Utwórz klasę podstawową, która zawiera metodę init () i metody rzutowania statycznego (dla pojedynczego obiektu i tablicy). Metody statyczne mogą być wszędzie; wersja z klasą bazową i init () pozwala później na łatwe rozszerzenia.

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() { }
}

Podobne mechanizmy (z przypisaniem () ) zostały wspomniane w poście @ Adam111p. Po prostu kolejny (bardziej kompletny) sposób na zrobienie tego. @Timothy Perez krytycznie odnosi się do przypisania () , ale imho jest tutaj w pełni odpowiednie.

Zaimplementuj pochodną (prawdziwą) klasę:

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

Teraz możemy rzutować obiekt pobrany z usługi:

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

Cała hierarchia obiektów SubjectArea będzie miała poprawną klasę.

Przypadek użycia / przykład; utwórz usługę Angular (abstrakcyjna klasa podstawowa ponownie):

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

Użycie staje się bardzo proste; utwórz usługę obszaru:

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

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

Metoda get () usługi zwróci obietnicę tablicy już rzutowanej jako obiekty SubjectArea (cała hierarchia)

Powiedzmy, że mamy inną klasę:

export class OtherItem extends ContentItem {...}

Tworzenie usługi, która pobiera dane i rzutuje do odpowiedniej klasy, jest tak proste, jak:

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

  constructor(protected http: Http) { super(); }
}
Normunds Kalnberzins
źródło
2

Użyj klasy rozszerzonej z interfejsu.

Następnie:

Object.assign(
                new ToWhat(),
                what
              )

A najlepiej:

Object.assign(
                    new ToWhat(),
                    <IDataInterface>what
                  )

ToWhat staje się kontrolerem DataInterface.

Sam
źródło
0

W Lates TS możesz to zrobić w następujący sposób:

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

A niż użytkownik taki:

if (isMyInterface(data)) {
 // now data will be type of MyInterface
}
Jaroslav
źródło
0

Wpadłem na podobną potrzebę. Chciałem czegoś, co da mi łatwą transformację z / do JSON, która pochodzi z wywołania interfejsu API REST do / z określonej definicji klasy. Rozwiązania, które znalazłem, były niewystarczające lub miały na celu przepisanie kodu moich klas i dodanie adnotacji lub podobnych.

Chciałem, aby coś takiego jak GSON było używane w Javie do serializacji / deserializacji klas do / z obiektów JSON.

W połączeniu z późniejszą potrzebą, aby konwerter działał również w JS, skończyłem pisać własny pakiet.

Ma jednak trochę narzutów. Ale po uruchomieniu jest bardzo wygodne w dodawaniu i edytowaniu.

Inicjujesz moduł za pomocą:

  1. schemat konwersji - pozwalający na mapowanie między polami i określanie sposobu konwersji
  2. Tablica map klas
  3. Mapa funkcji konwersji - do konwersji specjalnych.

Następnie w kodzie używasz zainicjowanego modułu, takiego jak:

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

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

lub do JSON:

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

Skorzystaj z tego łącza do pakietu npm, a także szczegółowe wyjaśnienie, jak pracować z modułem: konwerter json

Zapakowałem go również do
użytku Angular w: konwerter angular-json-class

Doronm
źródło
0

Przekaż obiekt bez zmian konstruktorowi klasy; Brak konwencji lub kontroli

interface iPerson {
   name: string;
   age: number;
}

class Person {
   constructor(private person: iPerson) { }

   toString(): string {
      return this.person.name + ' is ' + this.person.age;
   }  
}


// runs this as // 
const object1 = { name: 'Watson1', age: 64 };
const object2 = { name: 'Watson2' };            // age is missing

const person1 = new Person(object1);
const person2 = new Person(object2 as iPerson); // now matches constructor

console.log(person1.toString())  // Watson1 is 64
console.log(person2.toString())  // Watson2 is undefined
Lars Klingsten
źródło
0

Możesz użyć tego pakietu npm. https://www.npmjs.com/package/class-converter

Jest łatwy w użyciu, na przykład:

class UserModel {
  @property('i')
  id: number;

  @property('n')
  name: string;
}

const userRaw = {
  i: 1234,
  n: 'name',
};

// use toClass to convert plain object to class
const userModel = toClass(userRaw, UserModel);
// you will get a class, just like below one
// const userModel = {
//   id: 1234,
//   name: 'name',
// }
Gepard QC
źródło
0

Osobiście uważam za przerażające, że maszynopis nie pozwala definicji punktu końcowego określić typu otrzymywanego obiektu. Wygląda na to, że tak właśnie jest, zrobiłbym to, co zrobiłem z innymi językami, i oddzieliłbym obiekt JSON od definicji klasy, a definicja klasy użyłaby obiektu JSON jako jedynego elementu danych .

Nienawidzę kodu, który jest dla mnie bardzo ważny, więc dla mnie zazwyczaj chodzi o osiągnięcie pożądanego rezultatu przy najmniejszej ilości kodu przy zachowaniu typu.

Rozważ następujące definicje struktury obiektów JSON - byłyby to te, które otrzymasz w punkcie końcowym, są to tylko definicje struktury, bez metod.

interface IAddress {
    street: string;
    city: string;
    state: string;
    zip: string;
}

interface IPerson {
    name: string;
    address: IAddress;
}

Jeśli myślimy o powyższym w kategoriach obiektowych, powyższe interfejsy nie są klasami, ponieważ definiują jedynie strukturę danych. Klasa w kategoriach OO definiuje dane i kod, który na nich działa.

Definiujemy teraz klasę, która określa dane i działający na niej kod ...

class Person {
    person: IPerson;

    constructor(person: IPerson) {
        this.person = person;
    }

    // accessors
    getName(): string {
        return person.name;
    }

    getAddress(): IAddress {
        return person.address;
    }

    // You could write a generic getter for any value in person, 
    // no matter how deep, by accepting a variable number of string params

    // methods
    distanceFrom(address: IAddress): float {
        // Calculate distance from the passed address to this persons IAddress
        return 0.0;
    }
}

A teraz możemy po prostu przekazać dowolny obiekt zgodny ze strukturą IPerson i być w drodze ...

   Person person = new Person({
            name: "persons name",
            address: {
                street: "A street address",
                city: "a city",
                state: "a state",
                zip: "A zipcode"
            }
        });

W ten sam sposób możemy teraz przetwarzać obiekt otrzymany w twoim punkcie końcowym za pomocą czegoś podobnego do ...

Person person = new Person(req.body);    // As in an object received via a POST call

person.distanceFrom({ street: "Some street address", etc.});

Jest to o wiele bardziej wydajne i zużywa połowę pamięci na kopiowanie danych, przy jednoczesnym znacznym zmniejszeniu ilości kodu, który musisz napisać dla każdego typu encji. Po prostu polega na bezpieczeństwie typu zapewnianym przez TypeScript.

Rodney P. Barbati
źródło
0

Użyj deklaracji „jako”:

const data = JSON.parse(response.data) as MyClass;
Daniel Valdebenito
źródło
Ta technika została wspomniana w tej odpowiedzi sprzed dwóch lat i, jak zostało to omówione w innym miejscu, nie dodaje żadnych funkcji, które można zadeklarować MyClass.
Heretic Monkey
-1

Jest to prosta i naprawdę dobra opcja

let person = "{"name":"Sam","Age":"30"}";

const jsonParse: ((key: string, value: any) => any) | undefined = undefined;
let objectConverted = JSON.parse(textValue, jsonParse);

A potem będziesz miał

objectConverted.name
SamazoOo
źródło
-1

Korzystałem z tej biblioteki tutaj: https://github.com/pleerock/class-transformer

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

Realizacja:

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

Czasami trzeba przeanalizować wartości JSON dla plainToClass, aby zrozumieć, że są to dane sformatowane w JSON

Mac Chibueze
źródło
Biblioteka „klasy transformatora” jest już sugerowana w innej odpowiedzi powyżej stackoverflow.com/a/40042282/52277
Michael Freidgeim
-1

Korzystam z Angular 6 w interfejsie i aplikacji Spring Boot w backend, która zwraca obiekty Java. Wszystko, co muszę zrobić, to zdefiniować podobną klasę w aplikacji kątowej, która ma pasujące właściwości, a następnie mogę zaakceptować obiekt „jako” obiekt klasy kątowej ( porównaj jako Company w poniższym przykładzie).

Patrz na przykład kod interfejsu użytkownika poniżej. Daj mi znać w komentarzach, jeśli coś wymaga większej przejrzystości.

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

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

gdzie firma jest obiektem encji w wiosennej aplikacji rozruchowej, a także klasą w Angular.

export class Company {
    public id: number;
    public name: string;
    public owner: User;
    public locations: Array<Location>;
}
Maneesh Parihar
źródło