Custom object to JSON then back to a custom object?

13,204

Solution 1

One possible solution is the following:

function CardboardBox(n) {
  if(typeof(n) == 'string') {
    //build from name string
    this.name = n;
  } else {
    //build from object
    this.name = n.name;
  }

  //add in this object's "type" in a place
  //that is unlikely to exist in other JSON strings
  this.__type = 'CardboardBox';
}

var box = new CardboardBox("My Box");
send = JSON.stringify(box), // JSON CarboardBox()
obj = JSON.parse(send, function(key, val) {
  //if this is an object, and is CardboardBox
  if(typeof(val) === 'object' && val.__type === 'CardboardBox')
      return new CardboardBox(val);

  return val;

  //or if your object is in a context (like window), and there are many of
  //them that could be in there, you can do:
  //
  //if(typeof(val) === 'object' && context[val.__type])
  //    return new context[val.__type](val);
});


console.log(obj);

Basically store the object type in a place you know to look for later on when parsing the json. if you have multiple objects you can instantiate in a single scope the second parse method may be more appropriate. This also will account for objects in the JSON that are not CarboardBoxs.

Edit Here is a jsFiddle of this method in action.

Solution 2

Overall, you're correct: Javascript doesn't have any built-in way to serialize anything beyond plain objects, so going to and from JSON will not produce a particular class when you deserialize it. So you need to either work out serialization/deserialization yourself, or use a library that provides some support.

I personally like Backbone.js for this problem, as it handles serializing and deserializing quite well. You define a model class, which include a method to save its data to a server in a serialized form, and a method to deserialize it back to the model. The key design issue here is that deserializing is performed knowing the model you're deserializing to:

  • you either call myModel.fetch() to get data from the server based on the model id, or
  • you pass a bunch of new data to the model constructor: new Model(serializedData), or
  • you pass an array of data for multiple models to a collection that knows the model type: new ModelCollection(arrayOfSerializedData).

What Backbone doesn't do is deal with type-casting data of an unknown type. When I've dealt with this, I've usually done something similar to @Chad's response, but using an intermediary; you could see this as a proxy model, or as a factory:

var classes = {
    CardboardBox: ...,
    AluminumBox: ...
}

function Deserializer(json) {
    // parse if you're actually dealing with a string
    var data = JSON.parse(json),
        // now look for some custom type flag - you'll need to set this yourself
        type = data.type,
        // class lookup, perhaps with a default
        Cls = classes[type] || DefaultType;
    return new Cls(data);
}

var obj = new Deserializer(send);
obj instanceof CardboardBox; // should work

This still relies on a custom flag to switch types, though - I'm not sure there's any way around this.

Share:
13,204
Steve
Author by

Steve

Updated on July 01, 2022

Comments

  • Steve
    Steve almost 2 years

    I've seen very similar questions to this, but I can't quite decide if they was answered clearly - maybe I'm being a bit dense, sorry.

    I want to have the convenience (and clarity) of my own object, call it a CardboardBox(). It won't contain code, just data. I want to write this to a database and read it back later, but obviously, it is a type Object() when it's read back. All I can think of to find out what it used to be is:

    1. Have a member variable type that I set to CARDBOARD_BOX
    2. Instantiate a new CarbardBox() and use a function (in the box) to copy the properties of Object() to the new CardboardBox() object

    Is there a better way of doing this? I'm pretty sure I can change the actual type.

    function CardboardBox() { 
      this.type = "CARDBOARD_BOX"
      this.name = "No set";
      this.populate = new function(obj) {
        // populate this object with obj properties 
    }
    
    var box = new CarboardBox();  // CarboardBox
    box.name = "My Box";
    send = JSON.stringyfy(box);   
    .
    .
    .
    obj = JSON.parse(send);    // Object
    
    if (obj.type == "CARDBOARD_BOX") {
      savedBox = new CardboardBox();
      savedBox.populate(obj);
    }
    

    Thanks in advance... Steve

    [edit] My test code.

    function CardboardBox(n) {
      this.name = n;
    }
    
    var box = new CardboardBox("My Box");
    send = JSON.stringify(box); // JSON CarboardBox()
    
    obj = JSON.parse(send, function fn(obj) { // Object() returned
      log("OB: "+obj.type);
      return obj.type === 'CardboardBox' ? new CardboardBox(obj) : CardboardBox; 
    });     
    console.log(obj);
    

    Output is:

    OB: undefined utils.js:40
    OB: undefined utils.js:40
    function CardboardBox(n) {
        this.name = n;
    }