how to get length of javascript "array" - javascript

We have been using javascript "hashes" a lot lately, and we've been looking for a universal way to count the items contained in both arrays and hashes without having to "know" which we're dealing with except in the count method. As everyone knows .length is useless since it only returns the value of the highest index in the array. What we have below does not work because hashes test true for Array, but the length value returned is crap for hashes. We originally replaced .length all over our project with Object.keys().length, but this isn't supported in IE8 and lower.
This is such a stupid simple thing and we can't seem to get it working. Help me, Obi Wan. You're my only hope!
function isNullOrUndefined(aObject) {
"use strict";
return (typeof aObject === 'undefined' || aObject === null);
}
function count(aList) {
"use strict";
var lKey = null,
lResult = 0;
if (!isNullOrUndefined(aList)) {
if (aList.constructor == Array) {
lResult = aList.length;
} else if (!isNullOrUndefined(Object.keys)) {
lResult = Object.keys(aList).length;
} else {
for (lKey in aList) {
if (aList.hasOwnProperty(lKey)) {
lResult++;
}
}
}
}
return lResult;
}

Object.keys polyfill copied verbatim from the ES5-shim
// ES5 15.2.3.14
// http://es5.github.com/#x15.2.3.14
if (!Object.keys) {
// http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation
var hasDontEnumBug = true,
dontEnums = [
"toString",
"toLocaleString",
"valueOf",
"hasOwnProperty",
"isPrototypeOf",
"propertyIsEnumerable",
"constructor"
],
dontEnumsLength = dontEnums.length;
for (var key in {"toString": null}) {
hasDontEnumBug = false;
}
Object.keys = function keys(object) {
if ((typeof object != "object" && typeof object != "function") || object === null) {
throw new TypeError("Object.keys called on a non-object");
}
var keys = [];
for (var name in object) {
if (owns(object, name)) {
keys.push(name);
}
}
if (hasDontEnumBug) {
for (var i = 0, ii = dontEnumsLength; i < ii; i++) {
var dontEnum = dontEnums[i];
if (owns(object, dontEnum)) {
keys.push(dontEnum);
}
}
}
return keys;
};
}

Despise the answer from Raynos that is completely valid please consider performance
This is how my hash object look like
function Hash(){
this.values = [];
this.keys = {};
}
Hash.prototype.set = function(key, val){
if(this.keys[key]){
this.values[this.keys[key]] = value
}else{
this.keys[key] = (this.values.push(val)-1)
}
}
Hash.prototype.length = function(){
return this.values.length
}
Why I do this is simple performance looping through an object to count the properties length will be really inefficient the solution above give you direct access all the time.

Related

Why this code don't pollute the prototype in JavaScript?

I was watching some videos about prototype pollution in JavaScript, it seems that my code should be affected but testing code don't work:
// jQuery mock
var $ = {
isArray: (x) => x instanceof Array
};
var Clone = {
clone_object: function(object) {
var tmp = {};
if (typeof object === 'object') {
if ($.isArray(object)) {
return this.clone_array(object);
} else if (object === null) {
return object;
} else {
for (var key in object) {
if ($.isArray(object[key])) {
tmp[key] = this.clone_array(object[key]);
} else if (typeof object[key] === 'object') {
tmp[key] = this.clone_object(object[key]);
} else {
tmp[key] = object[key];
}
}
}
}
return tmp;
},
clone_array: function(array) {
if (!is_function(Array.prototype.map)) {
throw new Error("Your browser don't support ES5 array map " +
'use es5-shim');
}
return array.slice(0).map(function(item) {
if (typeof item === 'object') {
return this.clone_object(item);
} else {
return item;
}
}.bind(this));
}
};
var clone = function(object) {
return Clone.clone_object(object);
};
var x = JSON.parse('{"__proto__": {"foo": 10}}');
console.log(x);
var y = clone(x);
console.log(y);
var z = {};
console.log(z.foo);
Is prototype pollution something that don't work anymore or there is issue with my code? If issue is with the code what would need to be changed to pollution happen when calling clone function?
I think I know what the problem is:
This is vulnerable:
({}).__proto__.x = 10
console.log(({}).x);
but this is not
var tmp = {}
var tmp2 = {x: 10};
tmp.__proto__ = tmp2;
because the first example mutates the existing __proto__ object, but the second example replaces the whole __proto__ property, but doesn't mutate the previous __proto__ object.
The code in the question creates a new prototype object. It does not modify the prototype of a created object.

Javascript array find alternative

I'm looking for an alternative for the find() method of an array. I'm using it on Android Browser and Array.prototype.find() doesn't work there. Definition Array Support
var test= this.arrayOfValues.find(function (value) {
return (value.name === device.value)
});
If you do not care so much about programming your own, and if indexOf is somehow not usable, have a look at Underscore.js#find. As an alternative, as #NinaScholz recommended in the comments, use the Polyfill from mozilla.org:
if (!Array.prototype.find) {
Array.prototype.find = function(predicate) {
if (this === null) {
throw new TypeError('Array.prototype.find called on null or undefined');
}
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
var list = Object(this);
var length = list.length >>> 0;
var thisArg = arguments[1];
var value;
for (var i = 0; i < length; i++) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) {
return value;
}
}
return undefined;
};
}

Get objects position in array

I have an array players[]
function that gets certain object from such array by looking up its gameSocketId value and returns that object
getUserInfo : function(user)
{
var userInfo = Game.players.filter(function(e) {
return e.gameSocketId === user;
}).pop();
return userInfo;
}
so I store it in a variable like var user = getUserInfo(userId) How can I than find out what is the position of user in array of players[] knowing all info about it?
Use .findIndex:
getUserInfo : function(user)
{
var userInfoIndex = Game.players.findIndex(function(e) {
return e.gameSocketId === user;
});
return userInfoIndex;
}
Note that .findIndex, while fully specified is not included in most JS engines by default yet - there is a polyfill on mdn:
if (!Array.prototype.findIndex) {
Array.prototype.findIndex = function(predicate) {
if (this == null) {
throw new TypeError('Array.prototype.findIndex called on null or undefined');
}
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
var list = Object(this);
var length = list.length >>> 0;
var thisArg = arguments[1];
var value;
for (var i = 0; i < length; i++) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) {
return i;
}
}
return -1;
};
}
This polyfill works on ES3 and ES5 browsers just fine :)
Of course, one can also use a normal for loop to do this which works all the way through ES1 - but then you don't get the fun syntax that conveys intent pretty clearly:
getUserInfo : function(user) {
for(var i = 0; i < Game.players.length; i++){
if(Game.players[i].gameSocketId === user) return i;
}
return -1;
}
We don't always have to be clever :)
Of course, we can also always be inefficient and just call .indexOf after obtaining the item using your original method.
The second param of Array.filter is the index for the current item. The below will still return you the userInfo that you originally specified plus you can use the index for whatever you want.
getUserInfo : function(user)
{
var playerIndex;
var userInfo = Game.players.filter(function(e, index) {
if (e.gameSocketId === user) {
playerIndex = index;
return true;
}
}).pop();
console.log(Game.players[playerIndex]) // <- the player that is also "user"
return userInfo;
}
How about using indexof()
getUserInfo : function(user){
var userInfo = Game.players.filter(function(e) {
return e.gameSocketId === user;
}).pop();
return userInfo;
}
// and later
var user = getUserInfo(userId)
console.log(Game.players.indexOf(user));

Check if object has a set of properties in javascript

Let's say I have an object named a, how could I check that a has a specific list of multiple properties in shorthand, I think it can be done using in logical operator,
Something like this:
var a = {prop1:{},prop2:{},prop3:{}};
if ({1:"prop1",2:"prop2",3:"prop3"} in a)
console.log("a has these properties:'prop1, prop2 and prop3'");
EDIT
If plain javascript can't help, jQuery will do, but i prefer javascript
EDIT2
Portability is the privilege
The simplest away is to use a conventional &&:
if ("prop1" in a && "prop2" in a && "prop3" in a)
console.log("a has these properties:'prop1, prop2 and prop3'");
This isn't a 'shorthand', but it's not that much longer than what you've proposed.
You can also place the property names you want to test in an array and use the every method:
var propertiesToTest = ["prop1", "prop2", "prop3"];
if (propertiesToTest.every(function(x) { return x in a; }))
console.log("a has these properties:'prop1, prop2 and prop3'");
Note however, that this was introduced in ECMAScript 5, so it is not available on some older browsers. If this is a concern, you can provide your own version of it. Here's the implementation from MDN:
if (!Array.prototype.every) {
Array.prototype.every = function(fun /*, thisp */) {
'use strict';
var t, len, i, thisp;
if (this == null) {
throw new TypeError();
}
t = Object(this);
len = t.length >>> 0;
if (typeof fun !== 'function') {
throw new TypeError();
}
thisp = arguments[1];
for (i = 0; i < len; i++) {
if (i in t && !fun.call(thisp, t[i], i, t)) {
return false;
}
}
return true;
};
}
This is where the underscore.js library really shines. For instance it provides an already poly-filled every() method as suggested in a comment to p.s.w.g.'s answer: http://underscorejs.org/#every
But there's more than one way to do it; the following, while more verbose, may also suit your needs, and exposes you to more of what underscore can do (e.g. _.keys and _.intersection)
var a = {prop1:{},prop2:{},prop3:{}};
var requiredProps = ['prop1', 'prop2', 'prop3'];
var inBoth = _.intersection(_.keys(a), requiredProps);
if (inBoth.length === requiredProps.length) {
//code
}
Use Array.reduce like this:
var testProps = ['prop1', 'prop2', 'prop3'];
var a = {prop1:{},prop2:{},prop3:{}};
var result = testProps.reduce(function(i,j) { return i && j in a }, true);
console.log(result);
Like this:
var testProps = ['prop1', 'prop2', 'prop3', 'prop4'];
var num = -1, outA;
for(var i in a){
if(i === testProps[++num])outA[num] = i;
}
console.log('properties in a: '+outA.join(', '));
Slightly more elegant use of the Object.every() prototype function to include try-catch:
try {
const required = ['prop1', 'prop2', 'prop3']
const data = {prop1: 'hello', prop2: 'world', prop3: 'bad'}
if (!required.every( x => x in data )) throw new Error('missing property')
console.log('all properties found')
} catch(err) {
console.log(err.message)
}
``
I think it's good to try it:
/* Create an object class */
var obj = function(){ this.attributes = new Array(); }
obj.prototype.addProp = function(value){ this.attributes.push(new attribute(value)); }
obj.prototype.hasProp = function(value){
for(var i = 0; i < this.attributes.length; i++){
if(value == this.attributes[i].value) return true; } return false; }
function attribute(value){
this.value = value;
}
/* Testing object has some value*/
var ob = new obj();
ob.addProp('1');
ob.addProp('2');
ob.addProp('3');
//* check value index
//alert(ob.attributes[0].value);
//* check if ob has prop
alert(ob.hasProp('1'));
Here is DEMO

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