I once have written a JavaScript function that dumps a JavaScript object in a human readable format. It was something like JSON, but it also handled objects containing circular references shared object references. I must admit that it was written badly.
After reading this, I thought that maybe it's a good idea to change the previous code for serializing JavaScript objects. I also refactored the code. You can find it below. I didn't test it thoroughly, so be careful.
An example of what it can do:var x = {}; x.foo = new Object(); x.self = x; x.arr = [x.self]; x.bar = x.foo; var s = serialize(x); var y = deserialize(s); alert(y == y.self); // true alert(y == y.arr[0]); // true alert(y.foo == y.bar); // true alert(s); // { // 'foo': {}, // 'self': { _root_: [ ] }, // 'arr': [ // { _root_: [ ] } // ], // 'bar': { _root_: [ "foo" ] } // }Addition: I think that I have to express that better: don't expect it to serialize functions, regular expressions, images (or any other browser object), etc. It's just something like JSON, but it also handles shared object references. Code:
//============================================================================//
// JavaScript object de/serialization with circular references. //
// //
// author: Mehmet Yavuz Selim Soyturk //
// e-mail: Mehmet dot Yavuz dot Selim at gmail dot com //
//============================================================================//
Array.prototype.findIf = function(predicate) {
for(var i in this) {
if (predicate(this[i]))
return i;
}
return -1;
};
Array.prototype.map = function(func) {
var len = this.length;
var result = [];
for (var i=0; i<len; i++) {
result[i] = func(this[i]);
}
return result;
};
Array.prototype.appended = function(value) {
var copy = this.slice(0);
copy[copy.length] = value;
return copy;
};
//============================================================================//
// SERIALIZATION //
//============================================================================//
function indented(n) {
var s = '';
for(var i=0; i<n; i++) s += ' ';
return s;
}
function serializePrimitive(value) {
if (typeof value == 'string')
return '"' + value + '"';
else
return '' + value;
}
function serializePrimitiveArray(arr) {
var s = "[ ";
s += arr.map(serializePrimitive).join(', ');
s += " ]";
return s;
}
function serializeArray(arr, seen, indices, depth) {
if (arr.length == 0)
return '[]';
seen[seen.length] = {obj: arr, indices: indices};
var result = '[\n';
for (var i=0; i<arr.length; i++) {
result += indented(depth + 1);
result += serializeAny(arr[i], seen, indices.appended(i), depth + 1);
result += (i == arr.length - 1) ? '' : ', ';
result += '\n';
}
result += indented(depth) + ']';
return result;
}
function serializeObject(obj, seen, indices, depth) {
seen[seen.length] = {obj: obj, indices: indices};
var result = '{\n';
var count = 0;
for (var i in obj) {
if (typeof obj[i] != 'function') {
count++;
result += indented(depth+1);
result += "'" + i + "': ";
result += serializeAny(obj[i], seen, indices.appended(i), depth + 1);
result += ", \n"; // bad hack, see next
}
}
if (count > 0) {
result = result.substring(0, result.length-3) + '\n'; // bad hack
return result + indented(depth) + '}';
}
else {
return '{}';
}
}
function serializeAny(value, seen, indices, depth) {
var t = typeof(value);
var prevIndex;
if (t == 'function') {
throw new Error("Cannot serialize function. Keys from root: " +
serializePrimitiveArray(indices));
}
else if (t != 'object') {
return serializePrimitive(value);
}
else if ( (prevIndex = seen.findIf
( function(obj) { return obj.obj == value } )
) >= 0) {
return '{ _root_: ' + serializePrimitiveArray(seen[prevIndex].indices) + ' }';
}
else if (value.constructor == Array) {
return serializeArray(value, seen, indices, depth);
}
else {
return serializeObject(value, seen, indices, depth);
}
};
function serialize(obj) {
return serializeAny(obj, [], [], 0);
}
//============================================================================//
// DESERIALIZATION //
//============================================================================//
function followIndices(obj, indices) {
for (var i=0; i<indices.length; i++)
obj = obj[indices[i]];
return obj;
}
function replaceRootRefs(obj, root) {
if (typeof obj != 'object' || obj == null)
return;
for (var i in obj) {
var prop = obj[i];
if (typeof prop == 'object' && prop != null) {
if ('_root_' in prop)
obj[i] = followIndices(root, prop._root_);
replaceRootRefs(prop, root);
}
}
}
function deserialize(str) {
var obj = eval( '(' + str + ')' );
if (str.indexOf('_root_') < 0)
return obj;
replaceRootRefs(obj, obj);
return obj;
}
3 comments:
I use to load interface with Javascript with the toSource function, and i try your serialize library.
you say it can't run with a function, but i replace the throw error in your serializeAny function by "return value.toString();" and it works very well.
The link 'here' is broken. Could you fix it please?
@Chris, thanks. Added code in the blog post.
Post a Comment