I haven't found a simple method to do it, other people seem to be suggesting using custom replacer function in JSON.stringify to control which properties have been visited.
I've attempted to write such replacer:
function detector(obj) {
function collector (stack, key, val) {
var idx = stack[stack.length - 1].indexOf(key);
try {
var props = Object.keys(val);
if (!props.length) throw props;
props.unshift({idx : idx});
stack.push(props);
} catch (e) {
while (!(stack[stack.length - 1].length - 2)) {
idx = stack[stack.length -1][0].idx;
stack.pop();
}
if (idx + 1) {
stack[stack.length - 1].splice(idx, 1);
}
}
return val;
}
var stack = [[]];
try {
JSON.stringify(obj, collector.bind(null, stack));
} catch (e) {
if (e.message.indexOf('circular') !== -1) {
var idx = 0;
var path = '';
var parentProp = '';
while(idx + 1) {
idx = stack.pop()[0].idx;
parentProp = stack[stack.length - 1][idx];
if (!parentProp) break;
path = '.' + parentProp + path;
}
console.log(path);
}
}
}
What it does is while traversing the JSON tree (probably tree :)) it collects names of properties which have been visited and as soon as JSON.stringify detects circular reference and throws, 'stack' variable will contain a trace of which subtree it was traversing. And it logs this path to console.
However, this is not a heavily tested solution.