214

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.

BMiner
  • 16,669
  • 12
  • 53
  • 53
  • See also [Casting plain objects to function instances (“classes”) in javascript](http://stackoverflow.com/q/11810028/1048572) – Bergi Oct 31 '15 at 16:37

16 Answers16

181

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)
Erik van Velzen
  • 6,211
  • 3
  • 23
  • 23
  • 3
    Object.assign is not available in older browsers including IE and older Android browsers. http://kangax.github.io/compat-table/es6/#test-Object_static_methods_Object.assign – BMiner May 16 '17 at 14:10
  • 10
    There's also a big warning against using `Object.setPrototypeOf(...)`. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf – christo8989 Jul 13 '17 at 23:22
  • 1
    @SimonEpskamp That code does not work. Check your url, the second parameter to `setPrototypeOf` are property descriptors. – Erik van Velzen Aug 21 '17 at 11:20
  • 7
    Solution with setting prototype doesn't work if there is some property which also needs to have prototype. In other words: it only solves first level of data hierarchy. – Vojta Sep 10 '17 at 13:10
  • 3
    check out my solution below that applies Object.assign(..) recursively that can automatically resolve properties (with a bit of information provided in advance) – vir us Dec 08 '17 at 16:13
  • While I worked this up due it's simplicity, there's a more performant, generic solution on the bottom, with my single vote. It deserves also votes plus that solution has no warning: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf – baHI Oct 02 '19 at 05:47
  • Object.assign does work, but as others said, it is not recursively. – Marcel Nov 21 '21 at 12:05
81

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
Oliver Moran
  • 5,137
  • 4
  • 31
  • 45
  • I suppose you could do the "opposite" of this, as well. Construct a blank Foo Object and copy the properties from fooJSON into the new Foo Object. Finally, set fooJSON to point to the Foo Object. – BMiner May 03 '11 at 18:26
  • 9
    This is very dangerous. If the obj has an attribute that is not in Foo definition, you will create a Foo object with an extra hidden property that you don't know its name... Instead of a loop I will simply do: this.a = obj.a and this.b = obj.b. Or directly I would pass "a" and "b" as parameters: new Foo (obj.a, obj.b) – Gabriel Llamas May 03 '11 at 18:29
  • @GagleKas I wouldn't say dangerous. Hidden properties would be OK as long as you are aware of their existence. I am just trying to implement basic Object deserialization of a JSON Object. – BMiner May 03 '11 at 18:35
  • 3
    GagleKas's advice is worth listening to. (Although "very dangerous" is a little OTT.) The example is above is just to give you an idea. The correct implementation will depend on your application. – Oliver Moran May 03 '11 at 18:37
  • Well, instead of "very dangerous" you can put "is not recommended". If you want to add serialization/deserialization to an object look at my answer. – Gabriel Llamas May 03 '11 at 18:41
  • This solved my problem of trying to type cast from a JSON string to a type, allowing me to use the functions on my custom Type. – Darbio Oct 18 '11 at 04:59
  • 11
    You might want to protect yourself from prototype properties. `for (var prop in obj) {if (obj.hasOwnProperty(prop)) {this[prop] = obj[prop];}}` – Romain Vergnory Dec 19 '14 at 15:50
  • 4
    @RomainVergnory For even more safety, I only initialize properties created in the constructor, this instead of obj: `for (var prop in obj) {if (this.hasOwnProperty(prop)) {this[prop] = obj[prop];}}`. This assumes you expect the server to populate all properties, IMO should also throw if obj.hasOwnProperty() fails... – tekHedd Oct 26 '16 at 17:01
46

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.

Gabriel Llamas
  • 18,244
  • 26
  • 87
  • 112
  • 5
    I agree. This implementation will definitely work, and it's great... just a little wordy and specific to the Book Object. IMHO, the power of JS comes from prototypes and the ability to have some extra properties if you want to. That's all I'm sayin'. I was really looking for the one-liner: x.__proto__ = X.prototype; (although it's not IE browser compatible at this time) – BMiner May 03 '11 at 18:56
  • 6
    Don't forget that your `toJson()` method - regardless of whether it has individual properties hardcoded or uses a for each - will need to add backslash escape codes for some characters that could be in each string property. (A book title might have quotation marks, for example.) – nnnnnn May 04 '11 at 02:32
  • 1
    Yes, I know, my answer was an example and the best answer for the question, but... not even a positive point... I don't know why I waste my time helping others – Gabriel Llamas May 05 '11 at 13:45
  • 1
    I really appreciated your response and opinion, so you get the +1. It was definitely helpful; unfortunately, it isn't quite the best answer. Thanks! – BMiner May 13 '11 at 18:38
  • 12
    These days I would use `JSON.stringify()` instead of writing toJSon() myself. No need to reinvent the wheel now that all modern browsers support it. – stone Apr 26 '16 at 06:57
  • 2
    Agreed with @skypecakes. If you want to only serialize a subset of properties, create a constant of serializable properties. `serializable = ['title', 'author', ...]`. `JSON.stringify(serializable.reduce((obj, prop) => {...obj, [prop]: this[prop]}, {}))` – Atticus Aug 09 '16 at 03:30
  • If you want to serialize a subset, just implement `toJSON()` and return an anonymous object you what to serialize as `JSON.stringify()` will use [toJSON()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON()_behavior) automatically. – Erik Philips Jul 12 '19 at 10:04
20

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.

BMiner
  • 16,669
  • 12
  • 53
  • 53
  • 2
    Actually, something like that was my first instinct. – Oliver Moran May 03 '11 at 18:41
  • 16
    Note that `__proto__` has long been [deprecated](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto). Moreover, for performance reasons, it is not recommended to modify the [[Prototype]] internal property of an already created object (by setting `__proto__` or by any other means). – Yu Asakusa Feb 20 '14 at 10:20
  • 1
    Alas, none of the actually non-deprecated solutions are far more complex than this… – Wim Leers Apr 26 '16 at 16:57
  • I've made some tests of performance of changing `[[prototype]]` and it's seems to be irrelevant in Chrome. In firefox calling **new** is slower than using prototype, and Object.create is fastest. I guess issue with FF is that first test is slower than last, just order of execution matters. In chrome everything runs with almost same speed. I mean property access and nvocation of methods. Creatin is faster with new, but that's not so important. see: https://jsperf.com/prototype-change-test-8874874/1 and: https://jsperf.com/prototype-changed-method-call – Bogdan Mart Jan 26 '17 at 19:00
  • 6
    I suppose these days, one would call `Object.setPrototypeOf(fooJSON, Foo.prototype)` instead of setting `fooJSON.__proto__`... right? – stakx - no longer contributing Feb 21 '17 at 18:04
18

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.

Philipp Munin
  • 5,610
  • 7
  • 37
  • 60
13

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

4

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);
Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
4

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));
        }
    }
}
Jason Goemaat
  • 28,692
  • 15
  • 86
  • 113
2

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 }'));
Rob
  • 5,353
  • 33
  • 34
2

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.

Jelle De Loecker
  • 20,999
  • 27
  • 100
  • 142
2

A very simple way to get the desired effect is to add an type attribute while generating the json string, and use this string while parsing the string to generate the object:

    serialize = function(pObject) {
        return JSON.stringify(pObject, (key, value) => {
            if (typeof(value) == "object") {
                value._type = value.constructor.name;
            }
            return value;
        });
    }
    
    deSerialize = function(pJsonString) {
        return JSON.parse(pJsonString, (key, value) => {
            if (typeof(value) == "object" && value._type) {
                value = Object.assign(eval('new ' + value._type + '()'), value);
                delete value._type;
            }
            return value;
        });
    }

Here a little example of use:

    class TextBuffer {
        constructor() {
            this.text = "";
        }
        
        getText = function() {
            return this.text;
        }
        
        setText = function(pText) {
            this.text = pText;
        }
    }
    
    let textBuffer = new TextBuffer();
    textBuffer.setText("Hallo");
    console.log(textBuffer.getText()); // "Hallo"
    
    let newTextBuffer = deSerialize(serialize(textBuffer));
    console.log(newTextBuffer.getText()); // "Hallo"
Rüdiger
  • 21
  • 1
1

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
Remus
  • 61
  • 7
1

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);
}
vir us
  • 9,920
  • 6
  • 57
  • 66
0

Here is a solution using typescript and decorators.

  • Objects keep their methods after deserialization
  • Empty objects and their children are default-initialized

How to use it:

@SerializableClass
class SomeClass {
  serializedPrimitive: string;

  @SerializableProp(OtherSerializedClass)
  complexSerialized = new OtherSerializedClass();
}

@SerializableClass
class OtherSerializedClass {
  anotherPrimitive: number;

  someFunction(): void {
  }
}

const obj = new SomeClass();
const json = Serializable.serializeObject(obj);
let deserialized = new SomeClass();
Serializable.deserializeObject(deserialized, JSON.parse(json));
deserialized.complexSerialized.someFunction(); // this works!

How it works

Serialization:

  • Store the type name in the prototype (__typeName)

  • Use JSON.stringify with a replacer method that adds __typeName to the JSON.

Deserialization:

  • Store all serializable types in Serializable.__serializableObjects

  • Store a list of complex typed properties in every object (__serializedProps)

  • Initialize an object theObject via the type name and __serializableObjects.

  • Go through theObject.__serializedProps and traverse over it recursively (start at last step with every serialized property). Assign the results to the according property.

  • Use Object.assign to assign all remaining primitive properties.

The code:

// @Class decorator for serializable objects
export function SerializableClass(targetClass): void {
    targetClass.prototype.__typeName = targetClass.name;
    Serializable.__serializableObjects[targetClass.name] = targetClass;
}

// @Property decorator for serializable properties
export function SerializableProp(objectType: any) {
    return (target: {} | any, name?: PropertyKey): any => {
        if (!target.constructor.prototype?.__serializedProps)
            target.constructor.prototype.__serializedProps = {};
        target.constructor.prototype.__serializedProps[name] = objectType.name;
    };
}

export default class Serializable {
    public static __serializableObjects: any = {};

    private constructor() {
        // don't inherit from me!
    }

    static serializeObject(typedObject: object) {
        return JSON.stringify(typedObject, (key, value) => {
                if (value) {
                    const proto = Object.getPrototypeOf(value);
                    if (proto?.__typeName)
                        value.__typeName = proto.__typeName;
                }
                return value;
            }
        );
    }

    static deserializeObject(typedObject: object, jsonObject: object): object {
        const typeName = typedObject.__typeName;
        return Object.assign(typedObject, this.assignTypeRecursion(typeName, jsonObject));
    }

    private static assignTypeRecursion(typeName, object): object {
        const theObject = new Serializable.__serializableObjects[typeName]();
        Object.assign(theObject, object);
        const props = Object.getPrototypeOf(theObject).__serializedProps;
        for (const property in props) {
            const type = props[property];
            try {
                if (type == Array.name) {
                    const obj = object[property];
                    if (Array.isArray(obj)) {
                        for (let i = 0; i < obj.length; ++i) {
                            const arrItem = obj[i];
                            obj[i] = Serializable.assignTypeRecursion(arrItem.__typeName, arrItem);
                        }
                    } else
                        object[property] = [];
                } else
                    object[property] = Serializable.assignTypeRecursion(type, object[property]);
            } catch (e) {
                console.error(`${e.message}: ${type}`);
            }
        }
        return theObject;
    }
}

Comments Since I am a total js/ts newby (< 10 days), I am more than happy to receive any input/comments/suggestions. Here are some of my thoughts so far:

It could be cleaner: Unfortunately I did not find a way to get rid of the redundant parameter of @SerializableProp.

It could be more memory friendly: After you call serializeObject() every object stores __typeName which could massively blow up memory footprint. Fortunately __serializedProps is only stored once per class.

It could be more CPU friendly: It's the most inefficient code I've ever written. But well, it's just for web apps, so who cares ;-) Maybe one should at least get rid of the recursion.

Almost no error handling: well that's a task for another day

kunzej
  • 31
  • 5
0

class A {
  constructor (a) {
    this.a = a
  }
  method1 () {
    console.log('hi')
  }
}

var b = new A(1)

b.method1() // hi

var c = JSON.stringify(b)

var d = JSON.parse(c)
console.log(d.a) // 1
try {
  d.method1() // not a function
} catch {
  console.log('not a function')
} 

var e = Object.setPrototypeOf(d, A.prototype)

e.method1() // hi
Tyson
  • 9
  • 2
-4

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

Imal Hasaranga Perera
  • 9,683
  • 3
  • 51
  • 41