Skip to content

Instantly share code, notes, and snippets.

@rhysbrettbowen
Created August 15, 2012 04:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rhysbrettbowen/3355909 to your computer and use it in GitHub Desktop.
Save rhysbrettbowen/3355909 to your computer and use it in GitHub Desktop.
safe goog.json.serialize for cycles with description
The below I have changed from the goog.json.serialize. It's just a proof of concept so don't expect good code! it will return an array with two items. The first item is the json. The second will describe any cycles that were found and are structured like so:
[
//this is an array of the keys to get to the value where the cycle was
,
//this is the number of steps along the keys that the object was found
]
so if you're returned something like:
[['foo', 'bar'],1]
you can do:
obj['foo']['bar'] = obj['foo']
after parsing the object (you should be able to serialize the cycles as well and parse them later (I may write a parser if I can be bothered)
/**
* Serializes an object or a value to a JSON string.
*
* @param {*} object The object to serialize.
* @param {?goog.json.Replacer=} opt_replacer A replacer function
* called for each (key, value) pair that determines how the value
* should be serialized. By defult, this just returns the value
* and allows default serialization to kick in.
* @throws Error if there are loops in the object graph.
* @return {string} A JSON string representation of the input.
*/
goog.json.serialize = function(object, opt_replacer) {
// TODO(nicksantos): Change this to default to JSON.stringify when available.
// I need to fiddle with the default externs a bit to make this happen.
return new goog.json.Serializer(opt_replacer).serialize(object);
};
/**
* Class that is used to serialize JSON objects to a string.
* @param {?goog.json.Replacer=} opt_replacer Replacer.
* @constructor
*/
goog.json.Serializer = function(opt_replacer) {
/**
* @type {goog.json.Replacer|null|undefined}
* @private
*/
this.replacer_ = opt_replacer;
};
/**
* Serializes an object or a value to a JSON string.
*
* @param {*} object The object to serialize.
* @throws Error if there are loops in the object graph.
* @return {string} A JSON string representation of the input.
*/
goog.json.Serializer.prototype.serialize = function(object) {
var sb = [];
var trail = [];
var cycles = [];
var key = [];
this.serialize_(object, sb, trail, cycles, key);
return [sb.join(''), cycles];
};
/**
* Serializes a generic value to a JSON string
* @private
* @param {*} object The object to serialize.
* @param {Array} sb Array used as a string builder.
* @throws Error if there are loops in the object graph.
*/
goog.json.Serializer.prototype.serialize_ = function(object, sb, trail, cycles, keys) {
trail.push(object);
switch (typeof object) {
case 'string':
this.serializeString_((/** @type {string} */ object), sb);
break;
case 'number':
this.serializeNumber_((/** @type {number} */ object), sb);
break;
case 'boolean':
sb.push(object);
break;
case 'undefined':
sb.push('null');
break;
case 'object':
if (object == null) {
sb.push('null');
break;
}
if (goog.isArray(object)) {
this.serializeArray_((/** @type {!Array} */ object), sb, trail, cycles, keys);
break;
}
// should we allow new String, new Number and new Boolean to be treated
// as string, number and boolean? Most implementations do not and the
// need is not very big
this.serializeObject_((/** @type {Object} */ object), sb, trail, cycles, keys);
break;
case 'function':
// Skip functions.
// TODO(user) Should we return something here?
break;
default:
throw Error('Unknown type: ' + typeof object);
}
trail.pop();
};
/**
* Character mappings used internally for goog.string.quote
* @private
* @type {Object}
*/
goog.json.Serializer.charToJsonCharCache_ = {
'\"': '\\"',
'\\': '\\\\',
'/': '\\/',
'\b': '\\b',
'\f': '\\f',
'\n': '\\n',
'\r': '\\r',
'\t': '\\t',
'\x0B': '\\u000b' // '\v' is not supported in JScript
};
/**
* Regular expression used to match characters that need to be replaced.
* The S60 browser has a bug where unicode characters are not matched by
* regular expressions. The condition below detects such behaviour and
* adjusts the regular expression accordingly.
* @private
* @type {RegExp}
*/
goog.json.Serializer.charsToReplace_ = /\uffff/.test('\uffff') ?
/[\\\"\x00-\x1f\x7f-\uffff]/g : /[\\\"\x00-\x1f\x7f-\xff]/g;
/**
* Serializes a string to a JSON string
* @private
* @param {string} s The string to serialize.
* @param {Array} sb Array used as a string builder.
*/
goog.json.Serializer.prototype.serializeString_ = function(s, sb) {
// The official JSON implementation does not work with international
// characters.
sb.push('"', s.replace(goog.json.Serializer.charsToReplace_, function(c) {
// caching the result improves performance by a factor 2-3
if (c in goog.json.Serializer.charToJsonCharCache_) {
return goog.json.Serializer.charToJsonCharCache_[c];
}
var cc = c.charCodeAt(0);
var rv = '\\u';
if (cc < 16) {
rv += '000';
} else if (cc < 256) {
rv += '00';
} else if (cc < 4096) { // \u1000
rv += '0';
}
return goog.json.Serializer.charToJsonCharCache_[c] = rv + cc.toString(16);
}), '"');
};
/**
* Serializes a number to a JSON string
* @private
* @param {number} n The number to serialize.
* @param {Array} sb Array used as a string builder.
*/
goog.json.Serializer.prototype.serializeNumber_ = function(n, sb) {
sb.push(isFinite(n) && !isNaN(n) ? n : 'null');
};
/**
* Serializes an array to a JSON string
* @private
* @param {Array} arr The array to serialize.
* @param {Array} sb Array used as a string builder.
*/
goog.json.Serializer.prototype.serializeArray_ = function(arr, sb, trail, cycles, keys) {
var l = arr.length;
sb.push('[');
var sep = '';
for (var i = 0; i < l; i++) {
sb.push(sep);
var value = arr[i];
keys.push(i);
var ind = this.inArray(trail, value);
if(ind > -1) {
cycles.push([keys.slice(0), ind]);
sb.push('undefined');
}else
this.serialize_(
this.replacer_ ? this.replacer_.call(arr, String(i), value) : value,
sb, cycles, keys);
sep = ',';
keys.pop();
}
sb.push(']');
};
goog.json.Serializer.prototype.inArray = function(arr, obj) {
for(var i = 0; i < arr.length; i++)
if(arr[i] == obj)
return i;
return -1;
};
/**
* Serializes an object to a JSON string
* @private
* @param {Object} obj The object to serialize.
* @param {Array} sb Array used as a string builder.
*/
goog.json.Serializer.prototype.serializeObject_ = function(obj, sb, trail, cycles, keys) {
sb.push('{');
var sep = '';
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
keys.push(key);
var value = obj[key];
// Skip functions.
// TODO(ptucker) Should we return something for function properties?
var ind = this.inArray(trail, value);
if(ind > -1) {
cycles.push([keys.slice(0), ind]);
} else if (typeof value != 'function') {
sb.push(sep);
this.serializeString_(key, sb);
sb.push(':');
this.serialize_(
this.replacer_ ? this.replacer_.call(obj, key, value) : value,
sb, trail, cycles, keys);
sep = ',';
}
keys.pop();
}
}
sb.push('}');
};
@rhysbrettbowen
Copy link
Author

made in to a github repo: https://github.com/rhysbrettbowen/JSONycle

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment