How do I cast a JSON object to a typescript class

2018-12-31 04:11发布

I read a JSON object from a remote REST server. This JSON object has all the properties of a typescript class (by design). How do I cast that received JSON object to a type var?

I don't want to populate a typescript var (ie have a constructor that takes this JSON object). It's large and copying everything across sub-object by sub-object & property by property would take a lot of time.

Update: You can however cast it to a typescript interface!

16条回答
有味是清欢
2楼-- · 2018-12-31 04:45

In my case it works. I used functions Object.assign (target, sources ...). First, the creation of the correct object, then copies the data from json object to the target.Example :

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

And a more advanced example of use. An example using the array.

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;
  }
}
查看更多
宁负流年不负卿
3楼-- · 2018-12-31 04:46

I've created a simple tool to do this https://beshanoe.github.io/json2ts/ Unlike json2ts.com it works directly in browser and doesn't send your data to unknown servers. Also it has multiple settings. I'll work to improve it's functionality

查看更多
宁负流年不负卿
4楼-- · 2018-12-31 04:47

An old question with mostly correct, but not very efficient answers. This what I propose:

Create a base class that contains init() method and static cast methods (for a single object and an array). The static methods could be anywhere; the version with the base class and init() allows easy extensions afterwards.

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

Similar mechanics (with assign()) have been mentioned in @Adam111p post. Just another (more complete) way to do it. @Timothy Perez is critical of assign(), but imho it is fully appropriate here.

Implement a derived (the real) class:

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

Now we can cast an object retrieved from service:

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

All hierarchy of SubjectArea objects will have correct class.

A use case/example; create an Angular service (abstract base class again):

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

The usage becomes very simple; create an Area service:

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

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

get() method of the service will return a Promise of an array already cast as SubjectArea objects (whole hierarchy)

Now say, we have another class:

export class OtherItem extends ContentItem {...}

Creating a service that retrieves data and casts to the correct class is as simple as:

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

  constructor(protected http: Http) { super(); }
}
查看更多
余生无你
5楼-- · 2018-12-31 04:47

I see no mention of json-typescript-mapper: https://www.npmjs.com/package/json-typescript-mapper. It looks like a combination of @PhilipMiglinci's find, and @Pak's answer, as far as I can tell.

查看更多
与君花间醉酒
6楼-- · 2018-12-31 04:51

I used this library here: https://github.com/pleerock/class-transformer

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

Implementation:

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

Sometimes you will have to parse the JSON values for plainToClass to understand that it is a JSON formatted data

查看更多
不再属于我。
7楼-- · 2018-12-31 04:53

I had the same issue and I have found a library that does the job : https://github.com/pleerock/class-transformer.

It works like this :

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

It supports nested childs but you have to decorate your class's member.

查看更多
登录 后发表回答