197 lines
5.9 KiB
JavaScript
197 lines
5.9 KiB
JavaScript
var pathGetter = require('./path-getter');
|
|
var utils = require('./utils');
|
|
|
|
var WMap = typeof WeakMap !== 'undefined'?
|
|
WeakMap:
|
|
function() {
|
|
var keys = [];
|
|
var values = [];
|
|
return {
|
|
set: function(key, value) {
|
|
keys.push(key);
|
|
values.push(value);
|
|
},
|
|
get: function(key) {
|
|
for (var i = 0; i < keys.length; i++) {
|
|
if (keys[i] === key) {
|
|
return values[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Based on https://github.com/douglascrockford/JSON-js/blob/master/cycle.js
|
|
|
|
exports.decycle = function decycle(object, options, replacer) {
|
|
'use strict';
|
|
|
|
var map = new WMap()
|
|
|
|
var hasCircular = Object.prototype.hasOwnProperty.call(options, 'circular');
|
|
|
|
return (function derez(_value, path, key) {
|
|
|
|
// The derez recurses through the object, producing the deep copy.
|
|
|
|
var i, // The loop counter
|
|
name, // Property name
|
|
nu; // The new object or array
|
|
|
|
// typeof null === 'object', so go on if this value is really an object but not
|
|
// one of the weird builtin objects.
|
|
|
|
var value = replacer ? replacer(key || '', _value) : _value;
|
|
|
|
if (options.date && value instanceof Date) {
|
|
return {$jsan: 'd' + value.getTime()};
|
|
}
|
|
if (options.regex && value instanceof RegExp) {
|
|
return {$jsan: 'r' + utils.getRegexFlags(value) + ',' + value.source};
|
|
}
|
|
if (options['function'] && typeof value === 'function') {
|
|
return {$jsan: 'f' + utils.stringifyFunction(value, options['function'])}
|
|
}
|
|
if (options['nan'] && typeof value === 'number' && isNaN(value)) {
|
|
return {$jsan: 'n'}
|
|
}
|
|
if (options['infinity']) {
|
|
if (Number.POSITIVE_INFINITY === value) return {$jsan: 'i'}
|
|
if (Number.NEGATIVE_INFINITY === value) return {$jsan: 'y'}
|
|
}
|
|
if (options['undefined'] && value === undefined) {
|
|
return {$jsan: 'u'}
|
|
}
|
|
if (options['error'] && value instanceof Error) {
|
|
return {$jsan: 'e' + value.message}
|
|
}
|
|
if (options['symbol'] && typeof value === 'symbol') {
|
|
var symbolKey = Symbol.keyFor(value)
|
|
if (symbolKey !== undefined) {
|
|
return {$jsan: 'g' + symbolKey}
|
|
}
|
|
|
|
// 'Symbol(foo)'.slice(7, -1) === 'foo'
|
|
return {$jsan: 's' + value.toString().slice(7, -1)}
|
|
}
|
|
|
|
if (options['map'] && typeof Map === 'function' && value instanceof Map && typeof Array.from === 'function') {
|
|
return {$jsan: 'm' + JSON.stringify(decycle(Array.from(value), options, replacer))}
|
|
}
|
|
|
|
if (options['set'] && typeof Set === 'function' && value instanceof Set && typeof Array.from === 'function') {
|
|
return {$jsan: 'l' + JSON.stringify(decycle(Array.from(value), options, replacer))}
|
|
}
|
|
|
|
if (value && typeof value.toJSON === 'function') {
|
|
value = value.toJSON(key);
|
|
}
|
|
|
|
if (typeof value === 'object' && value !== null &&
|
|
!(value instanceof Boolean) &&
|
|
!(value instanceof Date) &&
|
|
!(value instanceof Number) &&
|
|
!(value instanceof RegExp) &&
|
|
!(value instanceof String) &&
|
|
!(typeof value === 'symbol') &&
|
|
!(value instanceof Error)) {
|
|
|
|
// If the value is an object or array, look to see if we have already
|
|
// encountered it. If so, return a $ref/path object.
|
|
|
|
if (typeof value === 'object' && value !== null) {
|
|
var foundPath = map.get(value);
|
|
if (foundPath) {
|
|
if (hasCircular && path.indexOf(foundPath) === 0) {
|
|
return typeof options.circular === 'function'?
|
|
options.circular(value, path, foundPath):
|
|
options.circular;
|
|
}
|
|
return {$jsan: foundPath};
|
|
}
|
|
map.set(value, path);
|
|
}
|
|
|
|
|
|
// If it is an array, replicate the array.
|
|
|
|
if (Object.prototype.toString.apply(value) === '[object Array]') {
|
|
nu = [];
|
|
for (i = 0; i < value.length; i += 1) {
|
|
nu[i] = derez(value[i], path + '[' + i + ']', i);
|
|
}
|
|
} else {
|
|
|
|
// If it is an object, replicate the object.
|
|
|
|
nu = {};
|
|
for (name in value) {
|
|
if (Object.prototype.hasOwnProperty.call(value, name)) {
|
|
var nextPath = /^\w+$/.test(name) ?
|
|
'.' + name :
|
|
'[' + JSON.stringify(name) + ']';
|
|
nu[name] = name === '$jsan' ? [derez(value[name], path + nextPath)] : derez(value[name], path + nextPath, name);
|
|
}
|
|
}
|
|
}
|
|
return nu;
|
|
}
|
|
return value;
|
|
}(object, '$'));
|
|
};
|
|
|
|
|
|
exports.retrocycle = function retrocycle($) {
|
|
'use strict';
|
|
|
|
|
|
return (function rez(value) {
|
|
|
|
// The rez function walks recursively through the object looking for $jsan
|
|
// properties. When it finds one that has a value that is a path, then it
|
|
// replaces the $jsan object with a reference to the value that is found by
|
|
// the path.
|
|
|
|
var i, item, name, path;
|
|
|
|
if (value && typeof value === 'object') {
|
|
if (Object.prototype.toString.apply(value) === '[object Array]') {
|
|
for (i = 0; i < value.length; i += 1) {
|
|
item = value[i];
|
|
if (item && typeof item === 'object') {
|
|
if (item.$jsan) {
|
|
value[i] = utils.restore(item.$jsan, $);
|
|
} else {
|
|
rez(item);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
for (name in value) {
|
|
// base case passed raw object
|
|
if(typeof value[name] === 'string' && name === '$jsan'){
|
|
return utils.restore(value.$jsan, $);
|
|
break;
|
|
}
|
|
else {
|
|
if (name === '$jsan') {
|
|
value[name] = value[name][0];
|
|
}
|
|
if (typeof value[name] === 'object') {
|
|
item = value[name];
|
|
if (item && typeof item === 'object') {
|
|
if (item.$jsan) {
|
|
value[name] = utils.restore(item.$jsan, $);
|
|
} else {
|
|
rez(item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return value;
|
|
}($));
|
|
};
|