187
votes

I know how to parse a JSON String and turn it into a JavaScript Object. You can use JSON.parse() in modern browsers (and IE9+).

That's great, but how can I take that JavaScript Object and turn it into a particular JavaScript Object (i.e. with a certain prototype)?

For example, suppose you have:

function Foo()
{
   this.a = 3;
   this.b = 2;
   this.test = function() {return this.a*this.b;};
}
var fooObj = new Foo();
alert(fooObj.test() ); //Prints 6
var fooJSON = JSON.parse({"a":4, "b": 3});
//Something to convert fooJSON into a Foo Object
//....... (this is what I am missing)
alert(fooJSON.test() ); //Prints 12

Again, I am not wondering how to convert a JSON string into a generic JavaScript Object. I want to know how to convert a JSON string into a "Foo" Object. That is, my Object should now have a function 'test' and properties 'a' and 'b'.

UPDATE After doing some research, I thought of this...

Object.cast = function cast(rawObj, constructor)
{
    var obj = new constructor();
    for(var i in rawObj)
        obj[i] = rawObj[i];
    return obj;
}
var fooJSON = Object.cast({"a":4, "b": 3}, Foo);

Will that work?

UPDATE May, 2017: The "modern" way of doing this, is via Object.assign, but this function is not available in IE 11 or older Android browsers.

13

13 Answers

143
votes

The current answers contain a lot of hand-rolled or library code. This is not necessary.

  1. Use JSON.parse('{"a":1}') to create a plain object.

  2. Use one of the standardized functions to set the prototype:

    • Object.assign(new Foo, { a: 1 })
    • Object.setPrototypeOf({ a: 1 }, Foo.prototype)
78
votes

See an example below (this example uses the native JSON object). My changes are commented in CAPITALS:

function Foo(obj) // CONSTRUCTOR CAN BE OVERLOADED WITH AN OBJECT
{
    this.a = 3;
    this.b = 2;
    this.test = function() {return this.a*this.b;};

    // IF AN OBJECT WAS PASSED THEN INITIALISE PROPERTIES FROM THAT OBJECT
    for (var prop in obj) this[prop] = obj[prop];
}

var fooObj = new Foo();
alert(fooObj.test() ); //Prints 6

// INITIALISE A NEW FOO AND PASS THE PARSED JSON OBJECT TO IT
var fooJSON = new Foo(JSON.parse('{"a":4,"b":3}'));

alert(fooJSON.test() ); //Prints 12
44
votes

Do you want to add JSON serialization/deserialization functionality, right? Then look at this:

You want to achieve this:

UML

toJson() is a normal method.
fromJson() is a static method.

Implementation:

var Book = function (title, author, isbn, price, stock){
    this.title = title;
    this.author = author;
    this.isbn = isbn;
    this.price = price;
    this.stock = stock;

    this.toJson = function (){
        return ("{" +
            "\"title\":\"" + this.title + "\"," +
            "\"author\":\"" + this.author + "\"," +
            "\"isbn\":\"" + this.isbn + "\"," +
            "\"price\":" + this.price + "," +
            "\"stock\":" + this.stock +
        "}");
    };
};

Book.fromJson = function (json){
    var obj = JSON.parse (json);
    return new Book (obj.title, obj.author, obj.isbn, obj.price, obj.stock);
};

Usage:

var book = new Book ("t", "a", "i", 10, 10);
var json = book.toJson ();
alert (json); //prints: {"title":"t","author":"a","isbn":"i","price":10,"stock":10}

var book = Book.fromJson (json);
alert (book.title); //prints: t

Note: If you want you can change all property definitions like this.title, this.author, etc by var title, var author, etc. and add getters to them to accomplish the UML definition.

20
votes

A blog post that I found useful: Understanding JavaScript Prototypes

You can mess with the __proto__ property of the Object.

var fooJSON = jQuery.parseJSON({"a":4, "b": 3});
fooJSON.__proto__ = Foo.prototype;

This allows fooJSON to inherit the Foo prototype.

I don't think this works in IE, though... at least from what I've read.

14
votes

Am I missing something in the question or why else nobody mentioned reviver parameter of JSON.parse since 2011?

Here is simplistic code for solution that works: https://jsfiddle.net/Ldr2utrr/

function Foo()
{
   this.a = 3;
   this.b = 2;
   this.test = function() {return this.a*this.b;};
}


var fooObj = new Foo();
alert(fooObj.test() ); //Prints 6
var fooJSON = JSON.parse(`{"a":4, "b": 3}`, function(key,value){
if(key!=="") return value; //logic of course should be more complex for handling nested objects etc.
  let res = new Foo();
  res.a = value.a;
  res.b = value.b;
  return res;
});
// Here you already get Foo object back
alert(fooJSON.test() ); //Prints 12

PS: Your question is confusing: >>That's great, but how can I take that JavaScript Object and turn it into a particular JavaScript Object (i.e. with a certain prototype)? contradicts to the title, where you ask about JSON parsing, but the quoted paragraph asks about JS runtime object prototype replacement.

4
votes

I like adding an optional argument to the constructor and calling Object.assign(this, obj), then handling any properties that are objects or arrays of objects themselves:

constructor(obj) {
    if (obj != null) {
        Object.assign(this, obj);
        if (this.ingredients != null) {
            this.ingredients = this.ingredients.map(x => new Ingredient(x));
        }
    }
}
3
votes

An alternate approach could be using Object.create. As first argument, you pass the prototype, and for the second one you pass a map of property names to descriptors:

function SomeConstructor() {
  
};

SomeConstructor.prototype = {
  doStuff: function() {
      console.log("Some stuff"); 
  }
};

var jsonText = '{ "text": "hello wrold" }';
var deserialized = JSON.parse(jsonText);

// This will build a property to descriptor map
// required for #2 argument of Object.create
var descriptors = Object.keys(deserialized)
  .reduce(function(result, property) {
    result[property] = Object.getOwnPropertyDescriptor(deserialized, property);
  }, {});

var obj = Object.create(SomeConstructor.prototype, descriptors);
2
votes

For the sake of completeness, here's a simple one-liner I ended up with (I had no need checking for non-Foo-properties):

var Foo = function(){ this.bar = 1; };

// angular version
var foo = angular.extend(new Foo(), angular.fromJson('{ "bar" : 2 }'));

// jquery version
var foo = jQuery.extend(new Foo(), jQuery.parseJSON('{ "bar" : 3 }'));
2
votes

I created a package called json-dry. It supports (circular) references and also class instances.

You have to define 2 new methods in your class (toDry on the prototype and unDry as a static method), register the class (Dry.registerClass), and off you go.

2
votes

The currently accepted answer wasn't working for me. You need to use Object.assign() properly:

class Person {
    constructor(name, age){
        this.name = name;
        this.age = age;
    }

    greet(){
        return `hello my name is ${ this.name } and i am ${ this.age } years old`;
    }
}

You create objects of this class normally:

let matt = new Person('matt', 12);
console.log(matt.greet()); // prints "hello my name is matt and i am 12 years old"

If you have a json string you need to parse into the Person class, do it like so:

let str = '{"name": "john", "age": 15}';
let john = JSON.parse(str); // parses string into normal Object type

console.log(john.greet()); // error!!

john = Object.assign(Person.prototype, john); // now john is a Person type
console.log(john.greet()); // now this works

1
votes

While, this is not technically what you want, if you know before hand the type of object you want to handle you can use the call/apply methods of the prototype of your known object.

you can change this

alert(fooJSON.test() ); //Prints 12

to this

alert(Foo.prototype.test.call(fooJSON); //Prints 12
1
votes

I've combined the solutions that I was able to find and compiled it into a generic one that can automatically parse a custom object and all it's fields recursively so you can use prototype methods after deserialization.

One assumption is that you defined a special filed that indicates it's type in every object you want to apply it's type automatically (this.__type in the example).

function Msg(data) {
    //... your init code
    this.data = data //can be another object or an array of objects of custom types. 
                     //If those objects defines `this.__type', their types will be assigned automatically as well
    this.__type = "Msg"; // <- store the object's type to assign it automatically
}

Msg.prototype = {
    createErrorMsg: function(errorMsg){
        return new Msg(0, null, errorMsg)
    },
    isSuccess: function(){
        return this.errorMsg == null;
    }
}

usage:

var responseMsg = //json string of Msg object received;
responseMsg = assignType(responseMsg);

if(responseMsg.isSuccess()){ // isSuccess() is now available
      //furhter logic
      //...
}

Type assignment function (it work recursively to assign types to any nested objects; it also iterates through arrays to find any suitable objects):

function assignType(object){
    if(object && typeof(object) === 'object' && window[object.__type]) {
        object = assignTypeRecursion(object.__type, object);
    }
    return object;
}

function assignTypeRecursion(type, object){
    for (var key in object) {
        if (object.hasOwnProperty(key)) {
            var obj = object[key];
            if(Array.isArray(obj)){
                 for(var i = 0; i < obj.length; ++i){
                     var arrItem = obj[i];
                     if(arrItem && typeof(arrItem) === 'object' && window[arrItem.__type]) {
                         obj[i] = assignTypeRecursion(arrItem.__type, arrItem);
                     }
                 }
            } else  if(obj && typeof(obj) === 'object' && window[obj.__type]) {
                object[key] = assignTypeRecursion(obj.__type, obj);
            }
        }
    }
    return Object.assign(new window[type](), object);
}
-4
votes

Olivers answers is very clear, but if you are looking for a solution in angular js, I have written a nice module called Angular-jsClass which does this ease, having objects defined in litaral notation is always bad when you are aiming to a big project but saying that developers face problem which exactly BMiner said, how to serialize a json to prototype or constructor notation objects

var jone = new Student();
jone.populate(jsonString); // populate Student class with Json string
console.log(jone.getName()); // Student Object is ready to use

https://github.com/imalhasaranga/Angular-JSClass