Skip to content

Instantly share code, notes, and snippets.

@Xunnamius
Created May 6, 2020 16:56
Show Gist options
  • Save Xunnamius/57e12bf40e373fd640ed44e29b618744 to your computer and use it in GitHub Desktop.
Save Xunnamius/57e12bf40e373fd640ed44e29b618744 to your computer and use it in GitHub Desktop.
Circular JSON Implementation: `uncirc` and `recirc` for stringifying/parsing circular objects in JavaScript
// Takes an object and replaces the circular references (including deep circular references) with strings
var uncirc = function(obj, ident, seen)
{
seen = seen || {};
// Push the object identity (key) and object (value) onto the "seen" list
seen[ident] = obj;
// Returns the ident of a value or null if we have not yet seen it
// Could have made this an anonymous function
function whatsTheIdentOf(value)
{
for(var key in seen)
{
if(seen.hasOwnProperty(key) && seen[key] === value)
return key;
}
return null;
}
// Iterate (deep) over the target object
for(var key in obj)
{
// Do not iterate up the prototype chain, please JavaScript... -_-
if(obj.hasOwnProperty(key))
{
var value = obj[key];
var seenIdent = whatsTheIdentOf(value);
// Have we seen this value before?
if(seenIdent !== null)
obj[key] = seenIdent;
// Catches arrays AND typical objects that have not been seen already
else if(typeof(value) == 'object' && value !== null)
uncirc(obj[key], ident + '.' + key, seen);
}
}
// Not really necessary since obj was passed by reference, but passing it back makes for a nice fluent interface
return obj;
};
// Takes an object with strings representing circular references and returns it to its original state
var recirc = function(obj, ident, rootObj)
{
rootObj = rootObj || obj;
// Takes a string and uses it to traverse an object
// For example:
// obj = { b: { c: 5 }}; can be traversed with the string 'b.c' which will
// return the integer 5
var crawlObjWithString = function(str, objkt)
{
objkt = objkt || obj;
var strSplit = str.split('.');
var specialKey = strSplit.shift();
var value = objkt[specialKey];
if(strSplit.length && typeof(value) == 'object' && value !== null)
return crawlObjWithString(strSplit.join('.'), objkt[specialKey]);
else
// Why not just return value instead? I wonder...
return objkt[specialKey];
};
for(var key in obj)
{
if(obj.hasOwnProperty(key))
{
var value = obj[key];
// Is this one of our special string values from uncirc?!
if(typeof(value) == 'string' && value.indexOf(ident) === 0)
obj[key] = value == ident ? rootObj : crawlObjWithString(value.substr(ident.length + 1), obj);
else if(typeof(value) == 'object' && value !== null)
recirc(obj[key], ident, obj);
}
}
// Not really necessary since obj was passed by reference, but passing it back makes for a nice fluent interface
return obj;
};
// How to use these two functions
var a = { b:1, c:2, d:3 };
a['e'] = a;
a['f'] = { a:1, b:2, c:4, d:8, e:16, f:32, g: { z: a }};
a.f.g.x = { wtf: a.f };
console.log(JSON.stringify(uncirc(a, 'LOL')));
console.log(recirc(a, 'LOL'));
// Play with it in node js on the terminal (naming this file `nodefun.js`):
// $ node
// > .load nodefun.js
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment