How do I cast a JSON Object to a TypeScript class?

JsonTypescript

Json Problem Overview


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!

Json Solutions


Solution 1 - Json

You can't simple cast a plain-old-JavaScript result from an Ajax request into a prototypical JavaScript/TypeScript class instance. There are a number of techniques for doing it, and generally involve copying data. Unless you create an instance of the class, it won't have any methods or properties. It will remain a simple JavaScript object.

While if you only were dealing with data, you could just do a cast to an interface (as it's purely a compile time structure), this would require that you use a TypeScript class which uses the data instance and performs operations with that data.

Some examples of copying the data:

  1. https://stackoverflow.com/questions/16373422/copying-ajax-json-object-into-existing-object
  2. https://stackoverflow.com/questions/5873624/parse-json-string-into-a-particular-object-prototype-in-javascript

In essence, you'd just :

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

Solution 2 - Json

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 children but you have to decorate your class's member.

Solution 3 - Json

In TypeScript you can do a type assertion using an interface and generics like so:

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

Where ILocationMap describes the shape of your data. The advantage of this method is that your JSON could contain more properties but the shape satisfies the conditions of the interface.

However, this does NOT add class instance methods.

Solution 4 - Json

If you are using ES6, try this:

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

But this method will not work on nested objects, sadly.

Solution 5 - Json

I found a very interesting article on generic casting of JSON to a Typescript Class:

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

You end up with following code:

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

Solution 6 - Json

There is nothing yet to automatically check if the JSON object you received from the server has the expected (read is conform to the) typescript's interface properties. But you can use User-Defined Type Guards

Considering the following interface and a silly json object (it could have been any type):

interface MyInterface {
    key: string;
 }

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

Three possible ways:

A. Type Assertion or simple static cast placed after the variable

const myObject: MyInterface = json as MyInterface;

B. Simple static cast, before the variable and between diamonds

const myObject: MyInterface = <MyInterface>json;

C. Advanced dynamic cast, you check yourself the structure of the object

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

You can play with this example here

Note that the difficulty here is to write the isMyInterface function. I hope TS will add a decorator sooner or later to export complex typing to the runtime and let the runtime check the object's structure when needed. For now, you could either use a json schema validator which purpose is approximately the same OR this runtime type check function generator

Solution 7 - Json

TLDR: One liner

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

The Detailed Answer

I would not recommend the Object.assign approach, as it can inappropriately litter your class instance with irrelevant properties (as well as defined closures) that were not declared within the class itself.

In the class you are trying to deserialize into, I would ensure any properties you want deserialized are defined (null, empty array, etc). By defining your properties with initial values you expose their visibility when trying to iterate class members to assign values to (see deserialize method below).

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

In the example above, I simply created a deserialize method. In a real world example, I would have it centralized in a reusable base class or service method.

Here is how to utilize this in something like an http resp...

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

If tslint/ide complains about argument type being incompatible, just cast the argument into the same type using angular brackets <YourClassName>, example:

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

If you have class members that are of a specific type (aka: instance of another class), then you can have them casted into typed instances through getter/setter methods.

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

The above example will deserialize both acct and the list of tasks into their respective class instances.

Solution 8 - Json

Assuming the json has the same properties as your typescript class, you don't have to copy your Json properties to your typescript object. You will just have to construct your Typescript object passing the json data in the constructor.

In your ajax callback, you receive a company:

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

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

In in order to to make that work:

  1. Add a constructor in your Typescript class that takes the json data as parameter. In that constructor you extend your json object with jQuery, like this: $.extend( this, jsonData). $.extend allows keeping the javascript prototypes while adding the json object's properties.

  2. Note you will have to do the same for linked objects. In the case of Employees in the example, you also create a constructor taking the portion of the json data for employees. You call $.map to translate json employees to typescript Employee objects.

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

    }

This is the best solution I found when dealing with Typescript classes and json objects.

Solution 9 - Json

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

Solution 10 - Json

While it is not casting per se; I have found https://github.com/JohnWhiteTB/TypedJSON to be a useful alternative.

@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"

Solution 11 - Json

Use a class extended from an interface.

Then:

    Object.assign(
        new ToWhat(),
        what
    )

And best:

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

ToWhat becomes a controller of DataInterface

Solution 12 - Json

Use 'as' declaration:

const data = JSON.parse(response.data) as MyClass;

Solution 13 - Json

Personally I find it appalling that typescript does not allow an endpoint definition to specify the type of the object being received. As it appears that this is indeed the case, I would do what I have done with other languages, and that is that I would separate the JSON object from the class definition, and have the class definition use the JSON object as its only data member.

I despise boilerplate code, so for me it is usually a matter of getting to the desired result with the least amount of code while preserving type.

Consider the following JSON object structure definitions - these would be what you would receive at an endpoint, they are structure definitions only, no methods.

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

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

If we think of the above in object oriented terms, the above interfaces are not classes because they only define a data structure. A class in OO terms defines data and the code that operates on it.

So we now define a class that specifies data and the code that operates on it...

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

And now we can simply pass in any object conforming to the IPerson structure and be on our way...

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

In the same fashion we can now process the object received at your endpoint with something along the lines of...

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

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

This is much more performant and uses half the memory of copying the data, while significantly reducing the amount of boilerplate code you must write for each entity type. It simply relies on the type safety provided by TypeScript.

Solution 14 - Json

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

Solution 15 - Json

You can create an interface of your type (SomeType) and cast the object in that.

const typedObject: SomeType = <SomeType> responseObject;

Solution 16 - Json

If you need to cast your json object to a typescript class and have its instance methods available in the resulting object you need to use Object.setPrototypeOf, like I did in the code snippet bellow:

Object.setPrototypeOf(jsonObject, YourTypescriptClass.prototype)

Solution 17 - Json

https://jvilk.com/MakeTypes/

you can use this site to generate a proxy for you. it generates a class and can parse and validate your input JSON object.

Solution 18 - Json

Instead a class may you could use a TypeScript interface.

Let's say we're working with a REST API that returns a JSON string representing a user. The JSON API agrees to a contract that it returns a string firstName, a string lastName and a number id. We model the user as an Interface in TypeScript as follow:

interface User {
   id: number;
   firstName: string;
   lastName: string;
}

The following code will be fine when you have the correct attributes map:

const json = '{"id": 1, "firstName": "Bob", "lastName": "Dylan"}'; //this will come from the REST API
try {
  const user: User = JSON.parse(json);
  console.log(user);
} catch (e) {
  console.log("An error on casting object has occurred", e);
}

Result:

[LOG]: {
  "id": 1,
  "firstName": "Bob",
  "lastName": "Dylan"
} 

Play this code here

Solution 19 - Json

FOR JAVA LOVERS

Make POJO class

export default class TransactionDTO{
    constructor() {
    }
}

create empty object by class

let dto = new TransactionDto()   // ts object
let json = {name:"Kamal",age:40} // js object

let transaction: TransactionDto = Object.assign(dto,JSON.parse(JSON.stringify(json)));//conversion

Solution 20 - Json

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

Solution 21 - Json

In the lates TS you can do like this:

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

And than user like this:

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

Solution 22 - Json

I ran into a similar need. I wanted something that will give me easy transformation from/to JSON that is coming from a REST api call to/from specific class definition. The solutions that I've found were insufficient or meant to rewrite my classes' code and adding annotations or similars.

I wanted something like GSON is used in Java to serialize/deserialize classes to/from JSON objects.

Combined with a later need, that the converter will function in JS as well, I ended writing my own package.

It has though, a little bit of overhead. But when started it is very convenient in adding and editing.

You initialize the module with :

  1. conversion schema - allowing to map between fields and determine how the conversion will be done
  2. Classes map array
  3. Conversion functions map - for special conversions.

Then in your code, you use the initialized module like :

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

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

or , to JSON :

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

Use this link to the npm package and also a detailed explanation to how to work with the module: json-class-converter

Also wrapped it for
Angular use in : angular-json-class-converter

Solution 23 - Json

Pass the object as is to the class constructor; No conventions or checks

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

Solution 24 - Json

You can use this npm package. https://www.npmjs.com/package/class-converter

It is easy to use, for example:

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',
// }

Solution 25 - Json

You can with a single tapi.js! It's a lightweight automapper that works in both ways.

npm i -D tapi.js

Then you can simply do

let typedObject = new YourClass().fromJSON(jsonData)

or with promises

axios.get(...).as(YourClass).then(typedObject => { ... })

You can read more about it on the docs.

Solution 26 - Json

This is a simple and a really good option

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

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

And then you'll have

objectConverted.name

Solution 27 - Json

You can cast json to property like this

class Jobs {
  constructor(JSONdata) {
    this.HEAT = JSONdata.HEAT;    
    this.HEAT_EAF = JSONdata.HEAT_EAF;    
  }
  
}

 var job = new Jobs({HEAT:'123',HEAT_EAF:'456'});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDavid ThielenView Question on Stackoverflow
Solution 1 - JsonWiredPrairieView Answer on Stackoverflow
Solution 2 - JsonPakView Answer on Stackoverflow
Solution 3 - Jsonuser756310View Answer on Stackoverflow
Solution 4 - Json小广东View Answer on Stackoverflow
Solution 5 - JsonPhilipView Answer on Stackoverflow
Solution 6 - JsonFlavien VolkenView Answer on Stackoverflow
Solution 7 - JsonTimothy PerezView Answer on Stackoverflow
Solution 8 - JsonAnthony BrenelièreView Answer on Stackoverflow
Solution 9 - JsonAdam111pView Answer on Stackoverflow
Solution 10 - JsonNeilView Answer on Stackoverflow
Solution 11 - JsonSamView Answer on Stackoverflow
Solution 12 - JsonDaniel ValdebenitoView Answer on Stackoverflow
Solution 13 - JsonRodney P. BarbatiView Answer on Stackoverflow
Solution 14 - JsonNormunds KalnberzinsView Answer on Stackoverflow
Solution 15 - JsonJayant VarshneyView Answer on Stackoverflow
Solution 16 - Jsons_bigheadView Answer on Stackoverflow
Solution 17 - JsonAmir MehrabiView Answer on Stackoverflow
Solution 18 - JsonCassio SeffrinView Answer on Stackoverflow
Solution 19 - JsonNilupul HeshanView Answer on Stackoverflow
Solution 20 - JsonMac ChibuezeView Answer on Stackoverflow
Solution 21 - JsonJaroslavView Answer on Stackoverflow
Solution 22 - JsonDoronmView Answer on Stackoverflow
Solution 23 - JsonLars KlingstenView Answer on Stackoverflow
Solution 24 - JsonQC-cheetahView Answer on Stackoverflow
Solution 25 - JsonMattia S.View Answer on Stackoverflow
Solution 26 - JsonSamazoOoView Answer on Stackoverflow
Solution 27 - JsonashkufarazView Answer on Stackoverflow