JSON to TypeScript class instance?

JsonTypescriptDeserialization

Json Problem Overview


I've done quite some research, but I'm not totally satisfied with what I found. Just to be sure here's my question: What is actually the most robust and elegant automated solution for deserializing JSON to TypeScript runtime class instances?

Say I got this class:

class Foo {
  name: string;
  GetName(): string { return this.name };
}

And say I got this JSON string for deserialization:

{"name": "John Doe"}

What's the best and most maintainable solution for getting an instance of a Foo class with the name set to "John Doe" and the method GetName() to work? I'm asking very specifically because I know it's easy to deserialize to a pure data-object. I'm wondering if it's possible to get a class instance with working methods, without having to do any manual parsing or any manual data copying. If a fully automated solution isn't possible, what's the next best solution?

Json Solutions


Solution 1 - Json

This question is quite broad, so I'm going to give a couple of solutions.

Solution 1: Helper Method

Here's an example of using a Helper Method that you could change to fit your needs:

class SerializationHelper {
	static toInstance<T>(obj: T, json: string) : T {
		var jsonObj = JSON.parse(json);
		
		if (typeof obj["fromJSON"] === "function") {
			obj["fromJSON"](jsonObj);
		}
		else {
			for (var propName in jsonObj) {
				obj[propName] = jsonObj[propName]
			}
		}
		
		return obj;
	}
}

Then using it:

var json = '{"name": "John Doe"}',
    foo = SerializationHelper.toInstance(new Foo(), json);

foo.GetName() === "John Doe";

Advanced Deserialization

This could also allow for some custom deserialization by adding your own fromJSON method to the class (this works well with how JSON.stringify already uses the toJSON method, as will be shown):

interface IFooSerialized {
    nameSomethingElse: string;
}

class Foo {
  name: string;
  GetName(): string { return this.name }

  toJSON(): IFooSerialized {
	  return {
		  nameSomethingElse: this.name
	  };
  }
  
  fromJSON(obj: IFooSerialized) {
        this.name = obj.nameSomethingElse;
  }
}

Then using it:

var foo1 = new Foo();
foo1.name = "John Doe";

var json = JSON.stringify(foo1);

json === '{"nameSomethingElse":"John Doe"}';

var foo2 = SerializationHelper.toInstance(new Foo(), json);

foo2.GetName() === "John Doe";

Solution 2: Base Class

Another way you could do this is by creating your own base class:

class Serializable {
    fillFromJSON(json: string) {
        var jsonObj = JSON.parse(json);
        for (var propName in jsonObj) {
		    this[propName] = jsonObj[propName]
	    }
    }
}

class Foo extends Serializable {
    name: string;
    GetName(): string { return this.name }
}

Then using it:

var foo = new Foo();
foo.fillFromJSON(json);

There's too many different ways to implement a custom deserialization using a base class so I'll leave that up to how you want it.

Solution 2 - Json

You can now use Object.assign(target, ...sources). Following your example, you could use it like this:

class Foo {
  name: string;
  getName(): string { return this.name };
}

let fooJson: string = '{"name": "John Doe"}';
let foo: Foo = Object.assign(new Foo(), JSON.parse(fooJson));

console.log(foo.getName()); //returns John Doe

Object.assign is part of ECMAScript 2015 and is currently available in most modern browsers.

Solution 3 - Json

> What is actually the most robust and elegant automated solution for deserializing JSON to TypeScript runtime class instances?

Using property decorators with ReflectDecorators to record runtime-accessible type information that can be used during a deserialization process provides a surprisingly clean and widely adaptable approach, that also fits into existing code beautifully. It is also fully automatable, and works for nested objects as well.

An implementation of this idea is TypedJSON, which I created precisely for this task:

@JsonObject
class Foo {
    @JsonMember
    name: string;
    
    getName(): string { return this.name };
}

var foo = TypedJSON.parse('{"name": "John Doe"}', Foo);

foo instanceof Foo; // true
foo.getName(); // "John Doe"

Solution 4 - Json

Why could you not just do something like this?

class Foo {
  constructor(myObj){
     Object.assign(this, myObj);
  }
  get name() { return this._name; }
  set name(v) { this._name = v; }
}

let foo = new Foo({ name: "bat" });
foo.toJSON() //=> your json ...

Solution 5 - Json

The best solution I found when dealing with Typescript classes and json objects: 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.

export class Foo
{
    Name: string;
    getName(): string { return this.Name };

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

In your ajax callback, translate your jsons in a your typescript object like this:

onNewFoo( jsonFoos : any[] )
{
  let receviedFoos = $.map( jsonFoos, (json) => { return new Foo( json ); } );

  // then call a method:
  let firstFooName = receviedFoos[0].GetName();
}

If you don't add the constructor, juste call in your ajax callback:

let newFoo = new Foo();
$.extend( newFoo, jsonData);
let name = newFoo.GetName()

...but the constructor will be useful if you want to convert the children json object too. See my detailed answer here.

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
QuestionKlausView Question on Stackoverflow
Solution 1 - JsonDavid SherretView Answer on Stackoverflow
Solution 2 - JsonHugo LeaoView Answer on Stackoverflow
Solution 3 - JsonJohn WeiszView Answer on Stackoverflow
Solution 4 - JsonamcdnlView Answer on Stackoverflow
Solution 5 - JsonAnthony BrenelièreView Answer on Stackoverflow