Home > OS >  Map json array to typescript object array with constructor
Map json array to typescript object array with constructor

Time:08-11

So I have a json array like this:

[
  {
    "title": "This is the first title",
    "image": "img/first_img.png"
  },
  {
    "title": "This one is the second",
    "image": "img/second_img.png"
  }
]

My goal is to map the simple json array to typescript object array, which is defined as follows.

export class GenericField {
  public Required: boolean;
  public Disabled: boolean;

  constructor() {
    this.Required = false;
    this.Disabled = false;
  }
}

export class StringField extends GenericField {
  private readonly _minLength: number | null;
  private readonly _maxLength: number | null;
  private readonly _value: string;

  constructor(
    minLength: number | null,
    maxLength: number | null,
    value: string
  ) {
    super();
    this._minLength = minLength;
    this._maxLength = maxLength;
    this._value = value;
  }
// ...
}

export class UrlField extends GenericField {
  private _url: string;

  constructor(url: string) {
    super();
    this._url = url;
  }

  public get Url(): string {
    return this._url;
  }
}

export class SliderObject {
  public title: StringField;
  public image: UrlField;

  constructor(title: string, image: string) {
    this.title = new StringField(null, null, title);
    this.image = new UrlField(image);
    this.image.Disabled = true;
  }
}

When I map with class-transformer, the objects aren't mapped correctly. How do I achieve that I can access Required field and Disabled field, which is not present in json.

Stackblitz

See my StackBlitz example here

CodePudding user response:

It would be like the following (assuming response.data is where the JSON text is).

const items: Array<{title: string, image: string}> = JSON.parse(response.data)

const sliderObjects = items.map(item =>
    new SliderObject(item['title'], item['image']))

CodePudding user response:

You can come up with your own reviver and conventions in your models to support proper deserialization.

class SliderObject {
  public title: StringField;
  public image: UrlField;

  constructor(title: string, image: string) { /* class initialization */ }

  static fromJSON(data: { image: string; title: string; }): SliderObject {
    return new SliderObject(data.title, data.image);
  }
  static isDeserializableFromJSON(data: any): data is { image: string; title: string; } {
    return data && typeof(data.image) === 'string' && typeof(data.title) === 'string';
  }
}

function jsonReviver(_: string, value: unknown): unknown {
  if (SliderObject.isDeserializableFromJSON(value)) {
    return SliderObject.fromJSON(value);
  }
  return value;
}

console.log(JSON.parse(`[/* array data */]`, jsonReviver));

In the example above I have added to static methods to SliderObject to help with serialization and a custom json reviver function. The static methods detect if data create from JSON.parse can be used to create a sliderObject and the other actually creates the new class. The reviver will call the guard method and return a SliderObject if the guard passed.

There are lots ways to skin this cat. I personally eschew TypeScript classes in favor of interfaces which I find are more flexible. Why try to make TypeScript C# or Java? By using interfaces, I can largely avoid situations like these.

CodePudding user response:

Please check some updates that I made in order to have your object attributes accessible:

https://stackblitz.com/edit/new-project-w5b97l?file=src/app/field-definition.ts

A few notes:

  • The GET request from the slider_object.json file is NOT returning an Observable<SliderObject[]> but an Observable<SliderObjectDTO[]> (or some other name for the interface). An instance of SliderObject has the StringField attribute and the UrlField attribute, while what you are reading from the file are JSON objects (NOT SliderObject instances) which check the SliderObjectDTO interface, so they will have the title and image properties.

  • observables should follow this naming convention: observableName$, so with a $ symbol at the end, thus sliderObjects renamed to sliderObjects$

  • class attributes should not start with a capital letter, thus Required renamed to required, Disabled renamed to disabled. The same thing applies for the properties (getters)

  • Related