8

I'm unclear how serialization/de-serialization is supposed to work on typed objects in JavaScript. For example, I have a "MapLayer" object that contains various members and arrays. I have written (but not yet tested) the following code to attempt to serialize it:

MapLayer.prototype.serialize = function() {
   var result = "{tileset:tilesets." + tilesets.getTilesetName(this.tileset) + ",columns:" + this.columns + ",rows:" + this.rows + 
   ",offsetX:" + this.offsetX + ",offsetY:" + this.offsetY + ",currentX:" + this.currentX + ",currentY:" + this.currentY + 
   ",scrollRateX:" + this.scrollRateX + ",scrollRateY:" + this.scrollRateY + ",virtualColumns:" + this.virtualColumns + ",virtualRows:" + this.virtualRows +
   ",tiles:\"" + this.encodeTileData2() + "\"";
   for(key in this)
   {
      if(this[key] instanceof Sprite)
         result += "," + key + ":" + this[key].serialize();
   }
   return result;
}

My question is, how is the resulting object supposed to get deserialized as a MapLayer object rather than as a generic Object. And how are all the Sprite instances supposed to get deserialized as sprites. Should I be using "new MapLayer()" instead of "{}"? Or am I simply supposed to include the prototype and constructor properties of the object in the serialization? Anything else I'm missing? Am I doing this a stupid way? There are 2 reasons I'm not using generic serialization/de-serialization code:

  1. I want to serialize the tile data in an optimized format rather than storing a base-10 string representation for each tile, and have them all delimited by commas.
  2. I don't want to serialize the tileset as an object that gets constructed as a new object during de-serialization, but rather as a reference to an existing object. Is that possible using code like I have proposed above?

Edit: Excuse my lack of proper terminology; JavaScript is one of my less expert languages. What I mean when I said "Typed Object" is an object with a constructor. In this example, my constructor is:

function MapLayer(map, tileset, columns, rows, virtualColumns, virtualRows, offsetX, offsetY, scrollRateX, scrollRateY, priority, tileData) {
   this.map = map;
   this.tileset = tileset;
   this.columns = columns;
   this.rows = rows;
   this.offsetX = offsetX;
   this.offsetY = offsetY;
   this.currentX = offsetX;
   this.currentY = offsetY;
   this.scrollRateX = scrollRateX;
   this.scrollRateY = scrollRateY;
   if(tileData.length < columns * rows * 2)
      this.tiles = DecodeData1(tileData);
   else
      this.tiles = DecodeData2(tileData);
   this.virtualColumns = virtualColumns ? virtualColumns : columns;
   this.virtualRows = virtualRows ? virtualRows : rows;
}

Edit 2: Taking the code from Šime Vidas' answer, I have added a related object called "Device":

function Device( description, memory ) {
    this.description = description;
    this.memory = memory;
}

function Person( name, sex, age, devices ) {
    this.name = name;
    this.sex = sex; 
    this.age = age;
    this.devices = devices;
}

Person.deserialize = function ( input ) {
    var obj = JSON.parse( input );
    return new Person( obj.name, obj.sex, obj.age, obj.devices );
};

var device = new Device( 'Blackberry', 64);
var device2 = new Device( 'Playstation 3', 600000);
var person = new Person( 'John', 'male', 25, [device, device2] );

var string = JSON.stringify(person);
console.log( string );

var person2 = Person.deserialize( string );
console.log( person2 );
console.log( person2 instanceof Person );

Now the question is how best to incorporate such dependent objects, because once again, the "type" (prototype?) of the object gets lost by JSON. Instead of running the constructor, why don't we simply change the serialize and the de-serialize functions to ensure that the object only needs to be constructed once like this instead of created and copied?

Person.prototype.serialize = function () {
    var obj = this; 
    return '({ ' + Object.getOwnPropertyNames( this ).map( function ( key ) {
        var value = obj[key];
        if ( typeof value === 'string' ) { value = '"' + value + '"'; }
        return key + ': ' + value;
    }).join( ', ' ) + ',"__proto__":Person.prototype})'; 
};

Person.deserialize = function ( input ) {
    return eval( input );
};

Edit 3: Another problem I have is that JSON doesn't seem to work in IE9. I'm using this test file:

<html>
<head>
<title>Script test</title>
<script language="javascript">
console.log(JSON);
</script>
</head>
</html>

And the console outputs:

SCRIPT5009: 'JSON' is undefined 
test.html, line 5 character 1

Edit 4: To correct the JSON problem I must include the correct DOCTYPE tag at the beginning.

6
  • An object with a constructor other than "Object". I don't know what to call it. Commented Dec 28, 2011 at 15:29
  • Ah, you mean objects that are instances of your own constructors (MapLayer, Sprite, etc.)... Commented Dec 28, 2011 at 15:32
  • My suggestion would be a MapLayer.prototype.deserialize method which would take a string and return a MapLayer object. I'm working on an example... Commented Dec 28, 2011 at 15:36
  • So avoid JSON in this case then? Commented Dec 28, 2011 at 15:50
  • Does the string returned by JSON.stringify( yourMapLayerObj ) conform with your requirement? If not, then you obviously need a custom stringify/parse solution... Commented Dec 28, 2011 at 16:10

3 Answers 3

8

For a start, here is a simple example of custom serialization / deserialization:

function Person( name, sex, age ) {
    this.name = name;
    this.sex = sex;
    this.age = age;
}

Person.prototype.serialize = function () {
    var obj = this;
    return '{ ' + Object.getOwnPropertyNames( this ).map( function ( key ) {
        var value = obj[key];
        if ( typeof value === 'string' ) { value = '"' + value + '"'; }
        return '"' + key + '": ' + value;
    }).join( ', ' ) + ' }';
};

Person.deserialize = function ( input ) {
    var obj = JSON.parse( input );
    return new Person( obj.name, obj.sex, obj.age );
};

Usage:

First, we create a new instance object:

var person = new Person( 'John', 'male', 25 );

Now, we serialize that object into a string using the Person.prototype.serialize method:

var string = person.serialize();

This will give use this string:

{ "name": "John", "sex": "male", "age": 25 }

Finally, we deserialize that string using the Person.deserialize static method:

var person2 = Person.deserialize( string );

Now, person2 is an instance of Person and contains the same property values as the original person instance.

Live demo: http://jsfiddle.net/VMqQN/


Now, while the Person.deserialize static method is required in any case (it uses JSON.parse internally, and invokes the Person constructor to initialize a new instance), the Person.prototype.serialize method on the other hand, is only needed if the built-in JSON.stringify static method doesn't suffice.

In my example above var string = JSON.stringify( person ) would get the job done too, so a custom serialization mechanism is not needed. See here: http://jsfiddle.net/VMqQN/1/ However, your case is more complex, so you'll need to define a custom serialization function.

Sign up to request clarification or add additional context in comments.

12 Comments

I have updated the question. Why don't we set __proto__ instead of calling the constructor?
Another problem is, if I try to use JSON in IE, I get an error "JSON is undefined".
@BlueMonkMN Because __proto__ is non-standard and deprecated. It has never been implemented in IE (and never will be). The ECMAScript standard does not provide any means to set the prototype link of an object subsequently. ... Which version of IE are you using? The JSON object was added in IE8...
@BlueMonkMN Try console.log( JSON ) in IE. You sould get '[Object JSON]' in IE's console (F12 -> Console tab)...
@BlueMonkMN Ah that's because you didn't define a doctype. Add <!doctype html> as the first line of your HTML document. If you fail to define a valid doctype, IE does into quirks mode...
|
1

If you look at the ST-JS (http://st-js.org) project, it allows to create your object graph in Java on the server side, serialize it in JSON, and deserialize it on the client side (Javascript) in a typed manner, i.e. the objects will be instantiated using their constructor and you can call methods on the created objects.

To use ST-JS you should write your client code is Java, that is converted almost one-to-one in Javascript.

The AJAX/JSON chapter on the home page of the site explains you how to parse a JSON string while keeping the type information.

3 Comments

I think the OP was asking about techniques to use for their existing codebase, rather than a different framework to migrate to.
Yes you're somehow right. The problem I was pointing out is that in order to be able to deserialize in a generic way a graph of "typed" objects (like in Edit 2) you need to somehow store some metadata about your "typed objects", i.e. to tell to your deserializer that for example "devices" property of "Person" is an array of "Device".
If you look at our sources the parseJSON function (that one should be able to easily extract) uses a "static" field called "typeDescription" that is a simple map - that our framework generates, but that can be also created manually.
1

My question is, how is the resulting object supposed to get deserialized as a MapLayer object rather than as a generic Object. And how are all the Sprite instances supposed to get deserialized as sprites.

I've made an npm module named esserializer to solve this problem: save JavaScript class instance values during serialization, in plain JSON format, together with its class name information:

const ESSerializer = require('esserializer');
const serializedText = ESSerializer.serialize(anInstanceOfClassMapLayer);

Later on, during the deserialization stage (possibly on another machine), esserializer can recursively deserialize object instance, with all Class/Property/Method information retained, using the same class definition:

const deserializedObj = ESSerializer.deserialize(serializedText, [MapLayer, Sprite]);
// deserializedObj is a perfect copy of anInstanceOfClassMapLayer

Should I be using "new MapLayer()" instead of "{}"? Or am I simply supposed to include the prototype and constructor properties of the object in the serialization?

Inside ESSerializer, class name information is included during serialization, and all prototype properties are re-constructed during deserialization.

I don't want to serialize the tileset as an object that gets constructed as a new object during de-serialization, but rather as a reference to an existing object.

Unfortunately, it is impossible. The serialization happens on one machine and the deserialization may happen on another computer -- anyway, this is what serialization/deserialization supposed to do. Computer would never know "an existing object" on another machine.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.