Trying to solve Lowest common ancestor - javascript

var lowestCommonAncestor = function(root, p, q) {
// return the path to the node
let path = []
const search = (node, target) => {
if (node === null) return false
path.push(node)
if (node === target) return true
const leftSearched = search(node.left, target)
if (leftSearched) return true
const rightSearched = search(node.right,target)
if (rightSearched) return true
path.pop()
}
search(root, p)
const pathP = path
path = []
search(root, q)
const pathQ = path
let result
while(pathP.length > 0 && pathQ.length > 0 && pathP[0] === pathQ[0]) {
result = pathP[0]
pathP.shift()
pathQ.shift()
}
return result
};
console.log(lowestCommonAncestor([3,5,1,6,2,0,8,null,null,7,4],5,1));
Iam getting following error message
const leftSearched = search(node.left, target)
^
TypeError: Cannot read property 'left' of undefined
Could someone help me to fix this issue

Leet Code, as some other code challenge sites, will transform the array input (actually the text input having JSON notation) into an instance of TreeNode, and will pass that as argument to the function with your solution code.
When you want to run the solution locally, you'll have to take care of this transformation yourself. For that purpose you could make use of the fromList function -- specifically for binary trees.
NB: you have a bug in your search function. if (node === target) should be if (node.val === target).
// LeetCode template:
function TreeNode(val) {
this.val = val;
this.left = this.right = null;
}
// Tool to convert array input to a tree:
function fromList(values) {
if (!values) return;
let it = (function* () {
for (let value of values) {
yield value == null ? null : new TreeNode(value);
}
while (true) yield null;
})();
let root = it.next().value;
let nextlevel = [root];
while (nextlevel.length) {
let level = nextlevel;
nextlevel = [];
for (let node of level) {
if (node) {
node.left = it.next().value;
node.right = it.next().value;
nextlevel.push(node.left, node.right);
}
}
}
return root;
}
// Your function
var lowestCommonAncestor = function(root, p, q) {
// return the path to the node
let path = []
const search = (node, target) => {
if (node === null) return false;
path.push(node);
if (node.val === target) return true;
const leftSearched = search(node.left, target);
if (leftSearched) return true;
const rightSearched = search(node.right,target);
if (rightSearched) return true;
path.pop();
}
search(root, p);
const pathP = path;
path = [];
search(root, q);
const pathQ = path;
let result;
while(pathP.length > 0 && pathQ.length > 0 && pathP[0] === pathQ[0]) {
result = pathP[0];
pathP.shift();
pathQ.shift();
}
return result;
};
// Running your solution on some input
let input = [3,5,1,6,2,0,8,null,null,7,4];
// Make the conversion that LeetCode would do
let root = fromList(input);
let lca = lowestCommonAncestor(root,5,1);
// For your own purposes, just print the value of that node:
console.log(lca.val); // 3

Related

Javascript json add a child element dynamically [duplicate]

Suppose we are only given
var obj = {};
var propName = "foo.bar.foobar";
How can we set the property obj.foo.bar.foobar to a certain value (say "hello world")?
So I want to achieve this, while we only have the property name in a string:
obj.foo.bar.foobar = "hello world";
function assign(obj, prop, value) {
if (typeof prop === "string")
prop = prop.split(".");
if (prop.length > 1) {
var e = prop.shift();
assign(obj[e] =
Object.prototype.toString.call(obj[e]) === "[object Object]"
? obj[e]
: {},
prop,
value);
} else
obj[prop[0]] = value;
}
var obj = {},
propName = "foo.bar.foobar";
assign(obj, propName, "Value");
I know it's an old one, but I see only custom functions in answers.
If you don't mind using a library, look at lodash _.set and _.get function.
Since this question appears to be answered by incorrect answers, I'll just refer to the correct answer from a similar question
function setDeepValue(obj, value, path) {
if (typeof path === "string") {
var path = path.split('.');
}
if(path.length > 1){
var p=path.shift();
if(obj[p]==null || typeof obj[p]!== 'object'){
obj[p] = {};
}
setDeepValue(obj[p], value, path);
}else{
obj[path[0]] = value;
}
}
Use:
var obj = {};
setDeepValue(obj, 'Hello World', 'foo.bar.foobar');
edit: I've created a jsPerf.com testcase to compare the accepted answer with my version.
Turns out that my version is faster, especially when you go very deep.
http://jsfiddle.net/9YMm8/
var nestedObjectAssignmentFor = function(obj, propString, value) {
var propNames = propString.split('.'),
propLength = propNames.length-1,
tmpObj = obj;
for (var i = 0; i <= propLength ; i++) {
tmpObj = tmpObj[propNames[i]] = i !== propLength ? {} : value;
}
return obj;
}
var obj = nestedObjectAssignment({},"foo.bar.foobar","hello world");
​
​
All solutions overid any of the original data when setting so I have tweaked with the following, made it into a single object too:
var obj = {}
nestObject.set(obj, "a.b", "foo");
nestObject.get(obj, "a.b"); // returns foo
var nestedObject = {
set: function(obj, propString, value) {
var propNames = propString.split('.'),
propLength = propNames.length-1,
tmpObj = obj;
for (var i = 0; i <= propLength ; i++) {
if (i === propLength){
if(tmpObj[propNames[i]]){
tmpObj[propNames[i]] = value;
}else{
tmpObj[propNames[i]] = value;
}
}else{
if(tmpObj[propNames[i]]){
tmpObj = tmpObj[propNames[i]];
}else{
tmpObj = tmpObj[propNames[i]] = {};
}
}
}
return obj;
},
get: function(obj, propString){
var propNames = propString.split('.'),
propLength = propNames.length-1,
tmpObj = obj;
for (var i = 0; i <= propLength ; i++) {
if(tmpObj[propNames[i]]){
tmpObj = tmpObj[propNames[i]];
}else{
break;
}
}
return tmpObj;
}
};
Can also change functions to be an Oject.prototype method changing obj param to this:
Object.prototype = { setNested = function(){ ... }, getNested = function(){ ... } }
{}.setNested('a.c','foo')
Here is a get and set function i just compiled from a couple of threads + some custom code.
It will also create keys that don't exist on set.
function setValue(object, path, value) {
var a = path.split('.');
var o = object;
for (var i = 0; i < a.length - 1; i++) {
var n = a[i];
if (n in o) {
o = o[n];
} else {
o[n] = {};
o = o[n];
}
}
o[a[a.length - 1]] = value;
}
function getValue(object, path) {
var o = object;
path = path.replace(/\[(\w+)\]/g, '.$1');
path = path.replace(/^\./, '');
var a = path.split('.');
while (a.length) {
var n = a.shift();
if (n in o) {
o = o[n];
} else {
return;
}
}
return o;
}
Here is a simple function to do that using reference.
function setValueByPath (obj, path, value) {
var ref = obj;
path.split('.').forEach(function (key, index, arr) {
ref = ref[key] = index === arr.length - 1 ? value : {};
});
return obj;
}
You could split the path and make a check if the following element exist. If not assign an object to the new property.
Return then the value of the property.
At the end assign the value.
function setValue(object, path, value) {
var fullPath = path.split('.'),
way = fullPath.slice(),
last = way.pop();
way.reduce(function (r, a) {
return r[a] = r[a] || {};
}, object)[last] = value;
}
var object = {},
propName = 'foo.bar.foobar',
value = 'hello world';
setValue(object, propName, value);
console.log(object);
Here's one that returns the updated object
function deepUpdate(value, path, tree, branch = tree) {
const last = path.length === 1;
branch[path[0]] = last ? value : branch[path[0]];
return last ? tree : deepUpdate(value, path.slice(1), tree, branch[path[0]]);
}
const path = 'cat.dog';
const updated = deepUpdate('a', path.split('.'), {cat: {dog: null}})
// => { cat: {dog: 'a'} }
A very straightforward one.
This implementation should be very performant.
It avoids recursions, and function calls, while maintaining simplicity.
/**
* Set the value of a deep property, creating new objects as necessary.
* #param {Object} obj The object to set the value on.
* #param {String|String[]} path The property to set.
* #param {*} value The value to set.
* #return {Object} The object at the end of the path.
* #author github.com/victornpb
* #see https://stackoverflow.com/a/46060952/938822
* #example
* setDeep(obj, 'foo.bar.baz', 'quux');
*/
function setDeep(obj, path, value) {
const props = typeof path === 'string' ? path.split('.') : path;
for (var i = 0, n = props.length - 1; i < n; ++i) {
obj = obj[props[i]] = obj[props[i]] || {};
}
obj[props[i]] = value;
return obj;
}
/*********************** EXAMPLE ***********************/
const obj = {
hello : 'world',
};
setDeep(obj, 'root', true);
setDeep(obj, 'foo.bar.baz', 1);
setDeep(obj, ['foo','quux'], '😉');
console.log(obj);
// ⬇︎ Click "Run" below to see output
I was looking for an answer that does not overwrite existing values and was easily readable and was able to come up with this. Leaving this here in case it helps others with the same needs
function setValueAtObjectPath(obj, pathString, newValue) {
// create an array (pathComponents) of the period-separated path components from pathString
var pathComponents = pathString.split('.');
// create a object (tmpObj) that references the memory of obj
var tmpObj = obj;
for (var i = 0; i < pathComponents.length; i++) {
// if not on the last path component, then set the tmpObj as the value at this pathComponent
if (i !== pathComponents.length-1) {
// set tmpObj[pathComponents[i]] equal to an object of it's own value
tmpObj[pathComponents[i]] = {...tmpObj[pathComponents[i]]}
// set tmpObj to reference tmpObj[pathComponents[i]]
tmpObj = tmpObj[pathComponents[i]]
// else (IS the last path component), then set the value at this pathComponent equal to newValue
} else {
// set tmpObj[pathComponents[i]] equal to newValue
tmpObj[pathComponents[i]] = newValue
}
}
// return your object
return obj
}
Same as Rbar's answers, very useful when you're working with redux reducers. I use lodash clone instead of spread operator to support arrays too:
export function cloneAndPatch(obj, path, newValue, separator='.') {
let stack = Array.isArray(path) ? path : path.split(separator);
let newObj = _.clone(obj);
obj = newObj;
while (stack.length > 1) {
let property = stack.shift();
let sub = _.clone(obj[property]);
obj[property] = sub;
obj = sub;
}
obj[stack.shift()] = newValue;
return newObj;
}
Object.getPath = function(o, s) {
s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
s = s.replace(/^\./, ''); // strip a leading dot
var a = s.split('.');
for (var i = 0, n = a.length; i < n; ++i) {
var k = a[i];
if (k in o) {
o = o[k];
} else {
return;
}
}
return o;
};
Object.setPath = function(o, p, v) {
var a = p.split('.');
var o = o;
for (var i = 0; i < a.length - 1; i++) {
if (a[i].indexOf('[') === -1) {
var n = a[i];
if (n in o) {
o = o[n];
} else {
o[n] = {};
o = o[n];
}
} else {
// Not totaly optimised
var ix = a[i].match(/\[.*?\]/g)[0];
var n = a[i].replace(ix, '');
o = o[n][ix.substr(1,ix.length-2)]
}
}
if (a[a.length - 1].indexOf('[') === -1) {
o[a[a.length - 1]] = v;
} else {
var ix = a[a.length - 1].match(/\[.*?\]/g)[0];
var n = a[a.length - 1].replace(ix, '');
o[n][ix.substr(1,ix.length-2)] = v;
}
};
Here's a simple method that uses a scoped Object that recursively set's the correct prop by path.
function setObjectValueByPath(pathScope, value, obj) {
const pathStrings = pathScope.split('/');
obj[pathStrings[0]] = pathStrings.length > 1 ?
setObjectValueByPath(
pathStrings.splice(1, pathStrings.length).join('/'),
value,
obj[pathStrings[0]]
) :
value;
return obj;
}
How about a simple and short one?
Object.assign(this.origin, { [propName]: value })
You can use reduce : (you can test it by copy/paste on browser console)
const setValueOf = (obj, value, ...path) => {
path.reduce((o, level, idx) => {
if(idx === path.length -1) { o[level] = value }; // on last change the value of the prop
return o && o[level]; // return the prop
}, obj);
};
Example
let objExmp = {a: 'a', b: {b1: 'b1', b2: 'b2', b3: { b3_3 : 'default_value' } }};
setValueOf(objExmp, 'new_value' , 'b', 'b3', 'b3_3');
console.log('objExmp', objExmp); // prop changed to 'new_value'
You can split the string path by '.' and spread like :
setValueOf(objExmp, 'new_value' , ...'b.b3.b3_3'.split('.'));

Check Palindrome Linked List

I intended to reverse the original LinkedList and compare it with reversed one. If it is identical return true. However, I keep getting the wrong results. Where I did wrong?
var isPalindrome = function(head) {
if(head == null || head.next == null) return true;
let reversedHead = reverse(head)
let count = 0
while (head != null && reversedHead != null) {
if(head.val != reversedHead.val) {
return false
}
head = head.next
reversedHead = reversedHead.next
}
if (head == null && reversedHead == null){
return true
} else {
return false
}
};
var reverse =(head)=> {
let prev = null
let next = new ListNode()
let curr = new ListNode()
curr = head
while(curr){
next = curr.next
curr.next = prev
prev = curr
curr = next
}
return prev
}
After you call:
let reversedHead = reverse(head)
You will have reversed the list, i.e. the head has become the tail, and a tail's next reference is null. Hence your loop will quickly end, as after:
head = head.next
... head will be null.
You need to rethink the algorithm. There are several ways to do it...
Instead of an inplace reversal, you could let reverse return a completely new linked list that will have the reversed order:
function reverse(head) { // Does not modify the given list. Returns a new one.
let node = head;
let reversedHead = null;
while (node) {
reversedHead = new ListNode(node.val, reversedHead);
node = node.next;
}
return reversedHead;
}
I assume here that the ListNode constructor accepts a second argument:
class ListNode {
constructor(val, next=null) {
this.val = val;
this.next = next;
}
}

Recurse in Linked List

I have been practicing the linked list and wanted to implement the recurse on it, although in some cases I was able to implement it efficiently, in other cases I failed miserably at doing so. I would like to know a method to do the recursive so as not to have to use the "while" to go through the Linked List, I have used the recurse to go through the arrays but when I wanted to do it similar in this case it fails.
I don't have much experience in implementing recursion and wanted to apply it in this method to get more experience with it, but at least it helped me understand the Linked List more by having to do it over and over again. Thank you.
class Node {
// Accept arguments (the second one could be optional)
constructor(data, next) {
this.data = data;
this.next = next;
}
lastNode() { // new method that uses recursion
return this.next?.lastNode() || this;
}
}
class ListRecurse {
constructor() {
this.head = null;
this.size = 0;
}
add(data) {
let newNode = new Node(data); // No second argument. It has a default value
if (this.head === null) {
this.head = newNode;
} else {
// The lastNode implementation uses recursion:
this.head.lastNode().next = newNode;
}
this.size ++;
return this; // to allow chaining
}
insertAdd(data, index) {
if (index < 0 || index > this.size) {
return null;
}
let newNode = new Node(data);
let current = this.head;
let prev;
if (index === 0) {
newNode.next = current;
this.head = newNode;
}
else {
for (let i = 0; i < index; i++) {
prev = current;
current = current.next;
}
this.head.lastNode().next = current;
prev.next = newNode;
}
this.size++;
return this;
}
Print() {
if (!this.size) {
return null;
}
let current = this.head;
let result = "";
while(current) {
result += current.data += "=>";
current = current.next;
}
result += "X";
return result;
}
DeletexData(data) {
let current = this.head;
let prev = null;
if (this.head === null) {
return null;
}
else if (current.data === data) {
if(!prev) {
this.head = this.head.next;
}
else
prev.next = current.next
}
return this.SearchDelete(data)
}
SearchDelete (data) {
let current = this.head;
let prev = null;
while(current != null) {
if (current.data === data) {
if (!current.next) prev.next = null
else prev.next = current.next
this.size--;
return data;
}
prev = current;
current = current.next;
}
return null;
}
DeleteLastNode() {
let current = this.head;
if (current === null) {
return 1
}
else if (current.next === null) {
this.head = null;
}
else return this.LastNode()
};
LastNode() {
let current = this.head;
while (current.next.next != null) {
current = current.next;
}
current.next = null;
this.size--;
}
Search(data) {
let current = this.head;
if (current === null) {
return null;
}
else
return this.RainbowSix(data)
}
RainbowSix(data) {
let current = this.head;
while (current) {
if (current.data === data) {
return current;
}
current = current.next;
}
return null;
}
Size(){
return this.size
}
}
let list = new ListRecurse();
list.add(1).add(2).add(3).add(44).add(66);
list.insertAdd(33,0)
list.DeleteLastNode()
console.log(list.Search(3))
console.log(list.Size())
console.log(list.Print())
console.log(list);
This may or may not help. It suggests a substantially different way to build your lists.
The idea is that recursion, although occasionally used with Object-Oriented (OO) systems, is much more closely tied to Functional Programming (FP). So if you're going to use recursion on your lists, you might as well use it with FP lists.
Creating and manipulating lists is one of the strengths of FP, and we can write your code much more simply. We create a bare list of one item, 42 by calling const list1 = ins (42) (null). We prepend that with 17 by calling const list2 = ins (17) (list1). Or we can write a whole chain of these like this:
const list3 = ins (1) (ins (2) (ins (3) (ins (4) (ins (5) (null)))))
There are many differences from your code, but one of the most fundamental, is that this treats lists as immutable objects. None of our code will change a list, it will just create a new one with the altered properties.
This is what ins might look like:
const ins = (data) => (list) =>
({data, next: list})
We could choose to write this as (data, list) => ... instead of (data) => (list) => .... That's just a matter of personal preference about style.
But the basic construction is that a list is
a value
followed by either
another list
or null
Here is an implementation of these ideas:
const ins = (data) => (list) =>
({data, next: list})
const del = (target) => ({data, next}) =>
target == data ? next : next == null ? {data, next} : {data, next: del (target) (next)}
const delLast = ({data, next}) =>
next == null ? null : {data, next: delLast (next)}
const size = (list) =>
list == null ? 0 : 1 + size (list.next)
const search = (pred) => ({data, next}) =>
pred (data) ? {data, next} : next != null ? search (pred) (next) : null
const fnd = (target) =>
search ((data) => data == target)
const print = ({data, next}) =>
data + (next == null ? '' : ('=>' + print (next)))
const list1 = ins (1) (ins (2) (ins (3) (ins (44) (ins (66) (null)))))
const list2 = ins (33) (list1)
const list3 = delLast (list2)
console .log (fnd (3) (list3))
console .log (size (list3))
console .log (print (list3))
console .log (list3)
.as-console-wrapper {max-height: 100% !important; top: 0}
Note that all of these functions, except for ins and find are directly recursive. They all call themselves. And find simply delegates the recursive work to search.
It's too much to try to describe all of these functions, but lets look at two. print is a simple function.
const print = ({data, next}) =>
data + (next == null ? '' : ('=>' + print (next)))
We build our output string by including our data followed by one of two things:
an empty string, if next is null
'=>' plus the recursive print call on next, otherwise.
del is a somewhat more complex function:
const del = (target) => ({data, next}) =>
target == data
? next
: next == null
? {data, next: null}
: {data, next: del (target) (next)}
We test if our current data is the target we want to delete. If it is, we simply return the list stored as next.
If not, we check whether next is null. If it is, we return (a copy of) the current list. If it is not, then we return a new list formed by our current data and a recursive call to delete the target from the list stored as next.
If you want to learn more about these ideas, you probably want to search for "Cons lists" ("con" here is not the opposite of "pro", but has to do with "construct"ing something.)
I used different terms than are most commonly used there, but the ideas are much the same. If you run across the terms car and cdr, they are equivalent to our data and next, respectively.
I was working on this answer as Scott made his post, making most of this information redundant. There is a portion which shows how to couple OOP-style with functional (persistent) data structures which you should find helpful.
Similar to Scott's answer, we start by writing plain functions, no classes or methods. I'm going to place mine in a module named list.js -
// list.js
import { raise } from "./func"
const nil =
Symbol("nil")
const isNil = t =>
t === nil
const node = (value, next) =>
({ node, value, next })
const singleton = v =>
node(v, nil)
const fromArray = a =>
a.reduceRight((r, _) => node(_, r), nil)
const insert = (t, v, i = 0) =>
isNil(t)
? singleton(v)
: i > 0
? node(t.value, insert(t.next, v, i - 1))
: node(v, t)
const last = t =>
isNil(t)
? raise("cannot get last element of empty list")
: isNil(t.next)
? t.value
: last(t.next)
const search = (t, q) =>
isNil(t)
? undefined
: t.value === q
? t
: search(t.next, q)
const size = t =>
isNil(t)
? 0
: 1 + size(t.next)
const toString = t =>
isNil(t)
? "Nil"
: `${t.value}->${toString(t.next)}`
const toArray = t =>
isNil(t)
? []
: [ t.value, ...toArray(t.next) ]
Now we can implement our OOP-style, List interface. This gives you the chaining behaviour you want. Notice how the methods are simple wrappers around the plain functions we wrote earlier -
// list.js (continued)
class List
{ constructor(t = nil)
{ this.t = t }
isNil()
{ return isNil(this.t) }
size()
{ return size(this.t) }
add(v)
{ return new List(node(v, this.t)) }
insert(v, i)
{ return new List(insert(this.t, v, i)) }
toString()
{ return toString(this.t) }
}
Finally, make sure to export the parts of your module
// list.js (continued)
export { nil, isNil, node, singleton, fromArray, insert, last, search, size, toArray, toString }
export default List
The List interface allows you to do things in the familiar OOP ways -
import List from "../list"
const t = (new List).add(3).add(2).add(1)
console.log(t.toString())
// 1->2->3->Nil
console.log(t.insert(9, 0).toString())
// 9->1->2->3->Nil
console.log(t.isNil())
// false
console.log(t.size())
// 3
Or you can import your module and work in a more functional way -
import * as list from "../list"
const t = list.fromArray([1, 2, 3])
console.log(list.toString(t))
// 1->2->3->Nil
console.log(list.isNil(t))
// true
console.log(list.size(t))
// 3
I think the important lesson here is that the module functions can be defined once, and then the OOP interface can be added afterwards.
A series of tests ensures the information in this answer is correct. We start writing the plain function tests -
// list_test.js
import List, * as list from "../list.js"
import * as assert from '../assert.js'
import { test, symbols } from '../test.js'
await test("list.isNil", _ => {
assert.pass(list.isNil(list.nil))
assert.fail(list.isNil(list.singleton(1)))
})
await test("list.singleton", _ => {
const [a] = symbols()
const e = list.node(a, list.nil)
assert.equal(e, list.singleton(a))
})
await test("list.fromArray", _ => {
const [a, b, c] = symbols()
const e = list.node(a, list.node(b, list.node(c, list.nil)))
const t = [a, b, c]
assert.equal(e, list.fromArray(t))
})
await test("list.insert", _ => {
const [a, b, c, z] = symbols()
const t = list.fromArray([a, b, c])
assert.equal(list.fromArray([z,a,b,c]), list.insert(t, z, 0))
assert.equal(list.fromArray([a,z,b,c]), list.insert(t, z, 1))
assert.equal(list.fromArray([a,b,z,c]), list.insert(t, z, 2))
assert.equal(list.fromArray([a,b,c,z]), list.insert(t, z, 3))
assert.equal(list.fromArray([a,b,c,z]), list.insert(t, z, 99))
assert.equal(list.fromArray([z,a,b,c]), list.insert(t, z, -99))
})
await test("list.toString", _ => {
const e = "1->2->3->Nil"
const t = list.fromArray([1,2,3])
assert.equal(e, list.toString(t))
assert.equal("Nil", list.toString(list.nil))
})
await test("list.size", _ => {
const [a, b, c] = symbols()
assert.equal(0, list.size(list.nil))
assert.equal(1, list.size(list.singleton(a)))
assert.equal(2, list.size(list.fromArray([a,b])))
assert.equal(3, list.size(list.fromArray([a,b,c])))
})
await test("list.last", _ => {
const [a, b, c] = symbols()
const t = list.fromArray([a,b,c])
assert.equal(c, list.last(t))
assert.throws(Error, _ => list.last(list.nil))
})
await test("list.search", _ => {
const [a, b, c, z] = symbols()
const t = list.fromArray([a, b, c])
assert.equal(t, list.search(t, a))
assert.equal(list.fromArray([b, c]), list.search(t, b))
assert.equal(list.singleton(c), list.search(t, c))
assert.equal(undefined, list.search(t, z))
})
await test("list.toArray", _ => {
const [a,b,c] = symbols()
const e = [a,b,c]
const t = list.fromArray(e)
assert.equal(e, list.toArray(t))
})
Next we ensure the List interface behaves accordingly -
// list_test.js (continued)
await test("List.isNil", _ => {
assert.pass((new List).isNil())
assert.fail((new List).add(1).isNil())
})
await test("List.size", _ => {
const [a,b,c] = symbols()
const t1 = new List
const t2 = t1.add(a)
const t3 = t2.add(b)
const t4 = t3.add(c)
assert.equal(0, t1.size())
assert.equal(1, t2.size())
assert.equal(2, t3.size())
assert.equal(3, t4.size())
})
await test("List.toString", _ => {
const t1 = new List
const t2 = (new List).add(3).add(2).add(1)
assert.equal("Nil", t1.toString())
assert.equal("1->2->3->Nil", t2.toString())
})
await test("List.insert", _ => {
const t = (new List).add(3).add(2).add(1)
assert.equal("9->1->2->3->Nil", t.insert(9, 0).toString())
assert.equal("1->9->2->3->Nil", t.insert(9, 1).toString())
assert.equal("1->2->9->3->Nil", t.insert(9, 2).toString())
assert.equal("1->2->3->9->Nil", t.insert(9, 3).toString())
assert.equal("1->2->3->9->Nil", t.insert(9, 99).toString())
assert.equal("9->1->2->3->Nil", t.insert(9, -99).toString())
})
Dependencies used in this post -
func.raise - allows you to raise an error using an expression instead of a throw statement
// func.js
const raise = (msg = "", E = Error) => // functional throw
{ throw E(msg) }
// ...
export { ..., raise }

Iterative tree serialization function

Here is a visual representation of the tree I'm working with and the desired string serialization:
Here is a recursive solution:
function* serialize(root) {
if (root.length === 0)
return;
const [x, xs] = root;
yield x;
for (let i = 0; i < xs.length; i++)
yield* serialize(xs[i]);
yield ")";
}
const Node = (x, ...xs) => ([x, xs]);
const tree = Node("a",
Node("b",
Node("e"),
Node("f",
Node("k"))),
Node("c"),
Node("d",
Node("g"),
Node("h"),
Node("i"),
Node("j")));
console.log(
Array.from(serialize(tree)).join("")); // abe)fk)))c)dg)h)i)j)))
Apparently, it works but isn't stack safe for very deep trees. How can I transform it into its iterative counterpart?
I know that I need a stack for this. But I cant figure out the details. I'm in particular interested in the underlying mechanics of such a transformation.
I managed to create an iterative pre-order traversal, but oddly enough this didn't help with serializing it iteratively:
const Node = (x, ...xs) => ([x, xs]);
const tree = Node("a",
Node("b",
Node("e"),
Node("f",
Node("k"))),
Node("c"),
Node("d",
Node("g"),
Node("h"),
Node("i"),
Node("j")));
function* preOrderIt(root) {
if (root.length === 0)
return;
const stack = [root];
while (stack.length > 0) {
let [x, xs] = stack.pop();
for (let i = xs.length - 1; i >= 0; i--)
stack.push(xs[i]);
yield x;
}
}
console.log(
Array.from(preOrderIt(tree)).join("")); // abefkcdghij
Your serialization basically adds an extra dummy child to each node, so you have to "visit" it when iterating:
const Node = (x, ...xs) => ([x, xs]);
const tree = Node("a",
Node("b",
Node("e"),
Node("f",
Node("k"))),
Node("c"),
Node("d",
Node("g"),
Node("h"),
Node("i"),
Node("j")));
function* preOrderIt(root) {
if (root.length === 0)
return;
const stack = [root];
while (stack.length > 0) {
let [x, xs] = stack.pop();
if (x !== ')') // <-
stack.push([')', []]); // <-
for (let i = xs.length - 1; i >= 0; i--)
stack.push(xs[i]);
yield x;
}
}
console.log(
Array.from(preOrderIt(tree)).join(""));
One option would be to link the nodes. That way you can traverse the tree without keeping the position you are in:
const Node = (value, ...children) => {
const node = { value, children };
children.forEach(child => child.parent = node);
if(children.length > 1)
children.reduceRight((curr, prev) => ((prev.next = curr), prev));
return node;
};
How does that help? Well:
function serialize(node) {
let result = node.value;
while(node) {
// Deep first
while(node.children[0]) {
node = node.children[0];
result += node.value;
}
// then right
if(node.next) {
node = node.next;
result += ")" + node.value;
} else {
// and up at the last node
while(node && !node.next) {
node = node.parent;
result += ")";
}
result += ")";
if(node) {
node = node.next;
result += node.value;
}
}
}
return result;
}
A different approach by using an array for the levels and iterating deeper levels first.
function s(root) {
var i = 0,
node,
levels = [[root]],
result = '';
while (i !== -1) {
[node, ...levels[i]] = levels[i];
if (node) {
result += node[0];
if (node[1].length) {
levels[++i] = node[1];
continue;
}
} else {
i--;
}
result += ')';
}
return result.slice(0, -1); remove extra bracket for the outer missing array
}
const
node = (x, ...xs) => ([x, xs]),
tree = node("a", node("b", node("e"), node("f", node("k"))), node("c"), node("d", node("g"), node("h"), node("i"), node("j"))),
result = s(tree);
console.log(result);

Detecting and fixing circular references in JavaScript

Given I have a circular reference in a large JavaScript object
And I try JSON.stringify(problematicObject)
And the browser throws
"TypeError: Converting circular structure to JSON"
(which is expected)
Then I want to find the cause of this circular reference, preferably using Chrome developer tools? Is this possible? How do you find and fix circular references in a large object?
Pulled from http://blog.vjeux.com/2011/javascript/cyclic-object-detection.html. One line added to detect where the cycle is. Paste this into the Chrome dev tools:
function isCyclic (obj) {
var seenObjects = [];
function detect (obj) {
if (obj && typeof obj === 'object') {
if (seenObjects.indexOf(obj) !== -1) {
return true;
}
seenObjects.push(obj);
for (var key in obj) {
if (obj.hasOwnProperty(key) && detect(obj[key])) {
console.log(obj, 'cycle at ' + key);
return true;
}
}
}
return false;
}
return detect(obj);
}
Here's the test:
> a = {}
> b = {}
> a.b = b; b.a = a;
> isCyclic(a)
Object {a: Object}
"cycle at a"
Object {b: Object}
"cycle at b"
true
#tmack's answer is definitely what I was looking for when I found this question!
Unfortunately it returns many false positives - it returns true if an object is replicated in the JSON, which isn't the same as circularity. Circularity means that an object is its own child, e.g.
obj.key1.key2.[...].keyX === obj
I modified the original answer, and this is working for me:
function isCyclic(obj) {
var keys = [];
var stack = [];
var stackSet = new Set();
var detected = false;
function detect(obj, key) {
if (obj && typeof obj != 'object') { return; }
if (stackSet.has(obj)) { // it's cyclic! Print the object and its locations.
var oldindex = stack.indexOf(obj);
var l1 = keys.join('.') + '.' + key;
var l2 = keys.slice(0, oldindex + 1).join('.');
console.log('CIRCULAR: ' + l1 + ' = ' + l2 + ' = ' + obj);
console.log(obj);
detected = true;
return;
}
keys.push(key);
stack.push(obj);
stackSet.add(obj);
for (var k in obj) { //dive on the object's children
if (Object.prototype.hasOwnProperty.call(obj, k)) { detect(obj[k], k); }
}
keys.pop();
stack.pop();
stackSet.delete(obj);
return;
}
detect(obj, 'obj');
return detected;
}
Here are a few very simple tests:
var root = {}
var leaf = {'isleaf':true};
var cycle2 = {l:leaf};
var cycle1 = {c2: cycle2, l:leaf};
cycle2.c1 = cycle1
root.leaf = leaf
isCyclic(cycle1); // returns true, logs "CIRCULAR: obj.c2.c1 = obj"
isCyclic(cycle2); // returns true, logs "CIRCULAR: obj.c1.c2 = obj"
isCyclic(leaf); // returns false
isCyclic(root); // returns false
Here is MDN's approach to detecting and fixing circular references when using JSON.stringify() on circular objects: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value :
In a circular structure like the following
var circularReference = {otherData: 123};
circularReference.myself = circularReference;
JSON.stringify() will fail:
JSON.stringify(circularReference);
// TypeError: cyclic object value
To serialize circular references you can use a library that supports them (e.g. cycle.js) or implement a solution by yourself, which will require finding and replacing (or removing) the cyclic references by serializable values.
The snippet below illustrates how to find and filter (thus causing data loss) a cyclic reference by using the replacer parameter of JSON.stringify():
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
JSON.stringify(circularReference, getCircularReplacer());
// {"otherData":123}
You can also use JSON.stringify with try/catch
function hasCircularDependency(obj)
{
try
{
JSON.stringify(obj);
}
catch(e)
{
return e.includes("Converting circular structure to JSON");
}
return false;
}
Demo
function hasCircularDependency(obj) {
try {
JSON.stringify(obj);
} catch (e) {
return String(e).includes("Converting circular structure to JSON");
}
return false;
}
var a = {b:{c:{d:""}}};
console.log(hasCircularDependency(a));
a.b.c.d = a;
console.log(hasCircularDependency(a));
This is a fix for both #Trey Mack and #Freddie Nfbnm answers on the typeof obj != 'object' condition. Instead it should test if the obj value is not instance of object, so that it can also work when checking values with object familiarity (for example, functions and symbols (symbols aren't instance of object, but still addressed, btw.)).
I'm posting this as an answer since I can't comment in this StackExchange account yet.
PS.: feel free to request me to delete this answer.
function isCyclic(obj) {
var keys = [];
var stack = [];
var stackSet = new Set();
var detected = false;
function detect(obj, key) {
if (!(obj instanceof Object)) { return; } // Now works with other
// kinds of object.
if (stackSet.has(obj)) { // it's cyclic! Print the object and its locations.
var oldindex = stack.indexOf(obj);
var l1 = keys.join('.') + '.' + key;
var l2 = keys.slice(0, oldindex + 1).join('.');
console.log('CIRCULAR: ' + l1 + ' = ' + l2 + ' = ' + obj);
console.log(obj);
detected = true;
return;
}
keys.push(key);
stack.push(obj);
stackSet.add(obj);
for (var k in obj) { //dive on the object's children
if (obj.hasOwnProperty(k)) { detect(obj[k], k); }
}
keys.pop();
stack.pop();
stackSet.delete(obj);
return;
}
detect(obj, 'obj');
return detected;
}
CircularReferenceDetector
Here is my CircularReferenceDetector class which outputs all the property stack information where the circularly referenced value is actually located at and also shows where the culprit references are.
This is especially useful for huge structures where it is not obvious by the key which value is the source of the harm.
It outputs the circularly referenced value stringified but all references to itself replaced by "[Circular object --- fix me]".
Usage:
CircularReferenceDetector.detectCircularReferences(value);
Note:
Remove the Logger.* statements if you do not want to use any logging or do not have a logger available.
Technical Explanation:
The recursive function goes through all properties of the object and tests if JSON.stringify succeeds on them or not.
If it does not succeed (circular reference), then it tests if it succeeds by replacing value itself with some constant string. This would mean that if it succeeds using this replacer, this value is the being circularly referenced value. If it is not, it recursively goes through all properties of that object.
Meanwhile it also tracks the property stack to give you information where the culprit value is located at.
Typescript
import {Logger} from "../Logger";
export class CircularReferenceDetector {
static detectCircularReferences(toBeStringifiedValue: any, serializationKeyStack: string[] = []) {
Object.keys(toBeStringifiedValue).forEach(key => {
var value = toBeStringifiedValue[key];
var serializationKeyStackWithNewKey = serializationKeyStack.slice();
serializationKeyStackWithNewKey.push(key);
try {
JSON.stringify(value);
Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is ok`);
} catch (error) {
Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" JSON.stringify results in error: ${error}`);
var isCircularValue:boolean;
var circularExcludingStringifyResult:string = "";
try {
circularExcludingStringifyResult = JSON.stringify(value, CircularReferenceDetector.replaceRootStringifyReplacer(value), 2);
isCircularValue = true;
} catch (error) {
Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is not the circular source`);
CircularReferenceDetector.detectCircularReferences(value, serializationKeyStackWithNewKey);
isCircularValue = false;
}
if (isCircularValue) {
throw new Error(`Circular reference detected:\nCircularly referenced value is value under path "${Util.joinStrings(serializationKeyStackWithNewKey)}" of the given root object\n`+
`Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:\n${circularExcludingStringifyResult}\n`);
}
}
});
}
private static replaceRootStringifyReplacer(toBeStringifiedValue: any): any {
var serializedObjectCounter = 0;
return function (key: any, value: any) {
if (serializedObjectCounter !== 0 && typeof(toBeStringifiedValue) === 'object' && toBeStringifiedValue === value) {
Logger.error(`object serialization with key ${key} has circular reference to being stringified object`);
return '[Circular object --- fix me]';
}
serializedObjectCounter++;
return value;
}
}
}
export class Util {
static joinStrings(arr: string[], separator: string = ":") {
if (arr.length === 0) return "";
return arr.reduce((v1, v2) => `${v1}${separator}${v2}`);
}
}
Compiled JavaScript from TypeScript
"use strict";
const Logger_1 = require("../Logger");
class CircularReferenceDetector {
static detectCircularReferences(toBeStringifiedValue, serializationKeyStack = []) {
Object.keys(toBeStringifiedValue).forEach(key => {
var value = toBeStringifiedValue[key];
var serializationKeyStackWithNewKey = serializationKeyStack.slice();
serializationKeyStackWithNewKey.push(key);
try {
JSON.stringify(value);
Logger_1.Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is ok`);
}
catch (error) {
Logger_1.Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" JSON.stringify results in error: ${error}`);
var isCircularValue;
var circularExcludingStringifyResult = "";
try {
circularExcludingStringifyResult = JSON.stringify(value, CircularReferenceDetector.replaceRootStringifyReplacer(value), 2);
isCircularValue = true;
}
catch (error) {
Logger_1.Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is not the circular source`);
CircularReferenceDetector.detectCircularReferences(value, serializationKeyStackWithNewKey);
isCircularValue = false;
}
if (isCircularValue) {
throw new Error(`Circular reference detected:\nCircularly referenced value is value under path "${Util.joinStrings(serializationKeyStackWithNewKey)}" of the given root object\n` +
`Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:\n${circularExcludingStringifyResult}\n`);
}
}
});
}
static replaceRootStringifyReplacer(toBeStringifiedValue) {
var serializedObjectCounter = 0;
return function (key, value) {
if (serializedObjectCounter !== 0 && typeof (toBeStringifiedValue) === 'object' && toBeStringifiedValue === value) {
Logger_1.Logger.error(`object serialization with key ${key} has circular reference to being stringified object`);
return '[Circular object --- fix me]';
}
serializedObjectCounter++;
return value;
};
}
}
exports.CircularReferenceDetector = CircularReferenceDetector;
class Util {
static joinStrings(arr, separator = ":") {
if (arr.length === 0)
return "";
return arr.reduce((v1, v2) => `${v1}${separator}${v2}`);
}
}
exports.Util = Util;
Here is a Node ES6 version mixed from the answers from #Aaron V and #user4976005, it fixes the problem with the call to hasOwnProperty:
const isCyclic = (obj => {
const keys = []
const stack = []
const stackSet = new Set()
let detected = false
const detect = ((object, key) => {
if (!(object instanceof Object))
return
if (stackSet.has(object)) { // it's cyclic! Print the object and its locations.
const oldindex = stack.indexOf(object)
const l1 = `${keys.join('.')}.${key}`
const l2 = keys.slice(0, oldindex + 1).join('.')
console.log(`CIRCULAR: ${l1} = ${l2} = ${object}`)
console.log(object)
detected = true
return
}
keys.push(key)
stack.push(object)
stackSet.add(object)
Object.keys(object).forEach(k => { // dive on the object's children
if (k && Object.prototype.hasOwnProperty.call(object, k))
detect(object[k], k)
})
keys.pop()
stack.pop()
stackSet.delete(object)
})
detect(obj, 'obj')
return detected
})
There's a lot of answers here, but I thought I'd add my solution to the mix. It's similar to #Trey Mack's answer, but that solution takes O(n^2). This version uses WeakMap instead of an array, improving the time to O(n).
function isCyclic(object) {
const seenObjects = new WeakMap(); // use to keep track of which objects have been seen.
function detectCycle(obj) {
// If 'obj' is an actual object (i.e., has the form of '{}'), check
// if it's been seen already.
if (Object.prototype.toString.call(obj) == '[object Object]') {
if (seenObjects.has(obj)) {
return true;
}
// If 'obj' hasn't been seen, add it to 'seenObjects'.
// Since 'obj' is used as a key, the value of 'seenObjects[obj]'
// is irrelevent and can be set as literally anything you want. I
// just went with 'undefined'.
seenObjects.set(obj, undefined);
// Recurse through the object, looking for more circular references.
for (var key in obj) {
if (detectCycle(obj[key])) {
return true;
}
}
// If 'obj' is an array, check if any of it's elements are
// an object that has been seen already.
} else if (Array.isArray(obj)) {
for (var i in obj) {
if (detectCycle(obj[i])) {
return true;
}
}
}
return false;
}
return detectCycle(object);
}
And this is what it looks like in action.
> var foo = {grault: {}};
> detectCycle(foo);
false
> foo.grault = foo;
> detectCycle(foo);
true
> var bar = {};
> detectCycle(bar);
false
> bar.plugh = [];
> bar.plugh.push(bar);
> detectCycle(bar);
true
You can also use Symbols - thanks to that approach you won't have to mutate properties of the original object, apart from adding symbol for marking visited node.
It's cleaner and should be faster than gathering node properties and comparing with the object. It also has optional depth limitation if you don't want to serialize big nested values:
// Symbol used to mark already visited nodes - helps with circular dependencies
const visitedMark = Symbol('VISITED_MARK');
const MAX_CLEANUP_DEPTH = 10;
function removeCirculars(obj, depth = 0) {
if (!obj) {
return obj;
}
// Skip condition - either object is falsy, was visited or we go too deep
const shouldSkip = !obj || obj[visitedMark] || depth > MAX_CLEANUP_DEPTH;
// Copy object (we copy properties from it and mark visited nodes)
const originalObj = obj;
let result = {};
Object.keys(originalObj).forEach((entry) => {
const val = originalObj[entry];
if (!shouldSkip) {
if (typeof val === 'object') { // Value is an object - run object sanitizer
originalObj[visitedMark] = true; // Mark current node as "seen" - will stop from going deeper into circulars
const nextDepth = depth + 1;
result[entry] = removeCirculars(val, nextDepth);
} else {
result[entry] = val;
}
} else {
result = 'CIRCULAR';
}
});
return result;
}
This will result in an object that has all the circular dependencies stripped and also does not go deeper than given MAX_CLEANUP_DEPTH.
Using symbols is safe as long as you don't do any meta-programming stuff on the object - they are transparent and they are not enumerable, hence - they will not show in any standard operations on the object.
Also, returning a new, cleaned up object has an advantage of not mutating the original one if you need to perform any additional operations on it.
If you don't want CIRCULAR marking, you can just modify the code a bit, hence skipping object before actually performing operations on it (inside the loop):
originalObj[visitedMark] = true; // Mark current node as "seen" - will stop from going deeper into circulars
const val = originalObj[entry];
// Skip condition - either object is falsy, was visited or we go too deep
const shouldSkip = val[visitedMark] || depth > MAX_SANITIZATION_DEPTH;
if (!shouldSkip) {
if (typeof val === 'object') { // Value is an object - run object sanitizer
const nextDepth = depth + 1;
result[entry] = removeCirculars(val, nextDepth);
} else {
result[entry] = val;
}
}
I just made this. It may be dirty, but works anyway... :P
function dump(orig){
var inspectedObjects = [];
console.log('== DUMP ==');
(function _dump(o,t){
console.log(t+' Type '+(typeof o));
for(var i in o){
if(o[i] === orig){
console.log(t+' '+i+': [recursive]');
continue;
}
var ind = 1+inspectedObjects.indexOf(o[i]);
if(ind>0) console.log(t+' '+i+': [already inspected ('+ind+')]');
else{
console.log(t+' '+i+': ('+inspectedObjects.push(o[i])+')');
_dump(o[i],t+'>>');
}
}
}(orig,'>'));
}
Then
var a = [1,2,3], b = [a,4,5,6], c = {'x':a,'y':b};
a.push(c); dump(c);
Says
== DUMP ==
> Type object
> x: (1)
>>> Type object
>>> 0: (2)
>>>>> Type number
>>> 1: (3)
>>>>> Type number
>>> 2: (4)
>>>>> Type number
>>> 3: [recursive]
> y: (5)
>>> Type object
>>> 0: [already inspected (1)]
>>> 1: (6)
>>>>> Type number
>>> 2: (7)
>>>>> Type number
>>> 3: (8)
>>>>> Type number
This tells that c.x[3] is equal to c, and c.x = c.y[0].
Or, a little edit to this function can tell you what you need...
function findRecursive(orig){
var inspectedObjects = [];
(function _find(o,s){
for(var i in o){
if(o[i] === orig){
console.log('Found: obj.'+s.join('.')+'.'+i);
return;
}
if(inspectedObjects.indexOf(o[i])>=0) continue;
else{
inspectedObjects.push(o[i]);
s.push(i); _find(o[i],s); s.pop(i);
}
}
}(orig,[]));
}
Here is #Thomas's answer adapted for node:
const {logger} = require("../logger")
// Or: const logger = {debug: (...args) => console.log.call(console.log, args) }
const joinStrings = (arr, separator) => {
if (arr.length === 0) return "";
return arr.reduce((v1, v2) => `${v1}${separator}${v2}`);
}
exports.CircularReferenceDetector = class CircularReferenceDetector {
detectCircularReferences(toBeStringifiedValue, serializationKeyStack = []) {
Object.keys(toBeStringifiedValue).forEach(key => {
let value = toBeStringifiedValue[key];
let serializationKeyStackWithNewKey = serializationKeyStack.slice();
serializationKeyStackWithNewKey.push(key);
try {
JSON.stringify(value);
logger.debug(`path "${joinStrings(serializationKeyStack)}" is ok`);
} catch (error) {
logger.debug(`path "${joinStrings(serializationKeyStack)}" JSON.stringify results in error: ${error}`);
let isCircularValue;
let circularExcludingStringifyResult = "";
try {
circularExcludingStringifyResult = JSON.stringify(value, this.replaceRootStringifyReplacer(value), 2);
isCircularValue = true;
} catch (error) {
logger.debug(`path "${joinStrings(serializationKeyStack)}" is not the circular source`);
this.detectCircularReferences(value, serializationKeyStackWithNewKey);
isCircularValue = false;
}
if (isCircularValue) {
throw new Error(`Circular reference detected:\nCircularly referenced value is value under path "${joinStrings(serializationKeyStackWithNewKey)}" of the given root object\n`+
`Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:\n${circularExcludingStringifyResult}\n`);
}
}
});
}
replaceRootStringifyReplacer(toBeStringifiedValue) {
let serializedObjectCounter = 0;
return function (key, value) {
if (serializedObjectCounter !== 0 && typeof(toBeStringifiedValue) === 'object' && toBeStringifiedValue === value) {
logger.error(`object serialization with key ${key} has circular reference to being stringified object`);
return '[Circular object --- fix me]';
}
serializedObjectCounter++;
return value;
}
}
}
I converted the answer of Freddie Nfbnm to TypeScript:
export class JsonUtil {
static isCyclic(json) {
const keys = [];
const stack = [];
const stackSet = new Set();
let detected = false;
function detect(obj, key) {
if (typeof obj !== 'object') {
return;
}
if (stackSet.has(obj)) { // it's cyclic! Print the object and its locations.
const oldIndex = stack.indexOf(obj);
const l1 = keys.join('.') + '.' + key;
const l2 = keys.slice(0, oldIndex + 1).join('.');
console.log('CIRCULAR: ' + l1 + ' = ' + l2 + ' = ' + obj);
console.log(obj);
detected = true;
return;
}
keys.push(key);
stack.push(obj);
stackSet.add(obj);
for (const k in obj) { // dive on the object's children
if (obj.hasOwnProperty(k)) {
detect(obj[k], k);
}
}
keys.pop();
stack.pop();
stackSet.delete(obj);
return;
}
detect(json, 'obj');
return detected;
}
}
Just to throw my version into the mix... below is a remix of #dkurzaj 's code (which is itself a remix of #Aaron V 's, #user4976005 's, #Trey Mack 's and finally #Freddie Nfbnm 's [removed?] code) plus #darksinge 's WeakMap idea. So... this thread's Megamix, I guess :)
In my version, a report (rather than console.log'ed entries) is optionally returned as an array of objects. If a report is not required, testing stops on the first sighting of a circular reference (a'la #darksinge 's code).
Further, hasOwnProperty has been removed as Object.keys returns only hasOwnProperty properties (see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys ).
function isCyclic(x, bReturnReport) {
var a_sKeys = [],
a_oStack = [],
wm_oSeenObjects = new WeakMap(), //# see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
oReturnVal = {
found: false,
report: []
}
;
//# Setup the recursive logic to locate any circular references while kicking off the initial call
(function doIsCyclic(oTarget, sKey) {
var a_sTargetKeys, sCurrentKey, i;
//# If we've seen this oTarget before, flip our .found to true
if (wm_oSeenObjects.has(oTarget)) {
oReturnVal.found = true;
//# If we are to bReturnReport, add the entries into our .report
if (bReturnReport) {
oReturnVal.report.push({
instance: oTarget,
source: a_sKeys.slice(0, a_oStack.indexOf(oTarget) + 1).join('.'),
duplicate: a_sKeys.join('.') + "." + sKey
});
}
}
//# Else if oTarget is an instanceof Object, determine the a_sTargetKeys and .set our oTarget into the wm_oSeenObjects
else if (oTarget instanceof Object) {
a_sTargetKeys = Object.keys(oTarget);
wm_oSeenObjects.set(oTarget /*, undefined*/);
//# If we are to bReturnReport, .push the current level's/call's items onto our stacks
if (bReturnReport) {
if (sKey) { a_sKeys.push(sKey) };
a_oStack.push(oTarget);
}
//# Traverse the a_sTargetKeys, pulling each into sCurrentKey as we go
//# NOTE: If you want all properties, even non-enumerables, see Object.getOwnPropertyNames() so there is no need to call .hasOwnProperty (per: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys)
for (i = 0; i < a_sTargetKeys.length; i++) {
sCurrentKey = a_sTargetKeys[i];
//# If we've already .found a circular reference and we're not bReturnReport, fall from the loop
if (oReturnVal.found && !bReturnReport) {
break;
}
//# Else if the sCurrentKey is an instanceof Object, recurse to test
else if (oTarget[sCurrentKey] instanceof Object) {
doIsCyclic(oTarget[sCurrentKey], sCurrentKey);
}
}
//# .delete our oTarget into the wm_oSeenObjects
wm_oSeenObjects.delete(oTarget);
//# If we are to bReturnReport, .pop the current level's/call's items off our stacks
if (bReturnReport) {
if (sKey) { a_sKeys.pop() };
a_oStack.pop();
}
}
}(x, '')); //# doIsCyclic
return (bReturnReport ? oReturnVal.report : oReturnVal.found);
}
Most of the other answers only show how to detect that an object-tree has a circular-reference -- they don't tell you how to fix those circular references (ie. replacing the circular-reference values with, eg. undefined).
The below is the function I use to replace all circular-references with undefined:
export const specialTypeHandlers_default = [
// Set and Map are included by default, since JSON.stringify tries (and fails) to serialize them by default
{type: Set, keys: a=>a.keys(), get: (a, key)=>key, delete: (a, key)=>a.delete(key)},
{type: Map, keys: a=>a.keys(), get: (a, key)=>a.get(key), delete: (a, key)=>a.set(key, undefined)},
];
export function RemoveCircularLinks(node, specialTypeHandlers = specialTypeHandlers_default, nodeStack_set = new Set()) {
nodeStack_set.add(node);
const specialHandler = specialTypeHandlers.find(a=>node instanceof a.type);
for (const key of specialHandler ? specialHandler.keys(node) : Object.keys(node)) {
const value = specialHandler ? specialHandler.get(node, key) : node[key];
// if the value is already part of visited-stack, delete the value (and don't tunnel into it)
if (nodeStack_set.has(value)) {
if (specialHandler) specialHandler.delete(node, key);
else node[key] = undefined;
}
// else, tunnel into it, looking for circular-links at deeper levels
else if (typeof value == "object" && value != null) {
RemoveCircularLinks(value, specialTypeHandlers, nodeStack_set);
}
}
nodeStack_set.delete(node);
}
For use with JSON.stringify specifically, simply call the function above prior to the stringification (note that it does mutate the passed-in object):
const objTree = {normalProp: true};
objTree.selfReference = objTree;
RemoveCircularLinks(objTree); // without this line, the JSON.stringify call errors
console.log(JSON.stringify(objTree));
if you just need to see the content of that circular object, just use console.table(circularObj)
Try using console.log() on the chrome/firefox browser to identify where the issue encountered.
On Firefox using Firebug plugin, you can debug your javascript line by line.
Update:
Refer below example of circular reference issue and which has been handled:-
// JSON.stringify, avoid TypeError: Converting circular structure to JSON
// Demo: Circular reference
var o = {};
o.o = o;
var cache = [];
JSON.stringify(o, function(key, value) {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
// Circular reference found, discard key
alert("Circular reference found, discard key");
return;
}
alert("value = '" + value + "'");
// Store value in our collection
cache.push(value);
}
return value;
});
cache = null; // Enable garbage collection
var a = {b:1};
var o = {};
o.one = a;
o.two = a;
// one and two point to the same object, but two is discarded:
JSON.stringify(o);
var obj = {
a: "foo",
b: obj
};
var replacement = {"b":undefined};
alert("Result : " + JSON.stringify(obj,replacement));
Refer example LIVE DEMO

Categories

Resources