Parse JSON String into a Particular Object Prototype in JavaScript

233,819

Solution 1

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)

Solution 2

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

Solution 3

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.

Solution 4

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.

Solution 5

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.

Share:
233,819

Related videos on Youtube

BMiner
Author by

BMiner

Updated on July 08, 2022

Comments

  • BMiner
    BMiner almost 2 years

    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
    BMiner about 13 years
    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.
  • Gabriel Llamas
    Gabriel Llamas about 13 years
    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)
  • BMiner
    BMiner about 13 years
    @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.
  • Oliver Moran
    Oliver Moran about 13 years
    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
    Oliver Moran about 13 years
    Actually, something like that was my first instinct.
  • Gabriel Llamas
    Gabriel Llamas about 13 years
    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.
  • BMiner
    BMiner about 13 years
    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)
  • nnnnnn
    nnnnnn about 13 years
    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.)
  • Gabriel Llamas
    Gabriel Llamas about 13 years
    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
  • BMiner
    BMiner about 13 years
    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!
  • Darbio
    Darbio over 12 years
    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.
  • Yu Asakusa
    Yu Asakusa about 10 years
    Note that __proto__ has long been deprecated. 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).
  • Romain Vergnory
    Romain Vergnory over 9 years
    You might want to protect yourself from prototype properties. for (var prop in obj) {if (obj.hasOwnProperty(prop)) {this[prop] = obj[prop];}}
  • stone
    stone about 8 years
    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.
  • Wim Leers
    Wim Leers about 8 years
    Alas, none of the actually non-deprecated solutions are far more complex than this…
  • Atticus
    Atticus almost 8 years
    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]}, {}))
  • tekHedd
    tekHedd over 7 years
    @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...
  • Bogdan Mart
    Bogdan Mart over 7 years
    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: jsperf.com/prototype-change-test-8874874/1 and: jsperf.com/prototype-changed-method-call
  • stakx - no longer contributing
    stakx - no longer contributing about 7 years
    I suppose these days, one would call Object.setPrototypeOf(fooJSON, Foo.prototype) instead of setting fooJSON.__proto__... right?
  • BMiner
    BMiner about 7 years
    Object.assign is not available in older browsers including IE and older Android browsers. kangax.github.io/compat-table/es6/…
  • christo8989
    christo8989 almost 7 years
    There's also a big warning against using Object.setPrototypeOf(...). developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • Erik van Velzen
    Erik van Velzen over 6 years
    @SimonEpskamp That code does not work. Check your url, the second parameter to setPrototypeOf are property descriptors.
  • Vojta
    Vojta over 6 years
    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.
  • vir us
    vir us over 6 years
    check out my solution below that applies Object.assign(..) recursively that can automatically resolve properties (with a bit of information provided in advance)
  • Erik Philips
    Erik Philips almost 5 years
    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() automatically.
  • baHI
    baHI over 4 years
    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: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • Marcel
    Marcel over 2 years
    Object.assign does work, but as others said, it is not recursively.