How to check the depth of an object? - javascript

I'm working on a permissions system with variable depth; depending on the complexity of a page, there could be more or less levels. I searched StackOverflow to find if this has been asked before, couldn't find it.
If I have this object:
{foo:{bar:{baz : 'baa'}}}
I need it to return 3, it has 3 levels to it.
With this object:
{abc: 'xyz'}
It would have to be 1.
This is what I have so far:
utils.depthOf = function(object, level){
// Returns an int of the deepest level of an object
level = level || 1;
var key;
for(key in object){
if (!object.hasOwnProperty(key)) continue;
if(typeof object[key] == 'object'){
level++;
level = utils.depthOf(object[key], level);
}
}
return level;
}
The problem is it counts sister elements too. It's actually not getting depth, it's counting all members of an object.

Well, here you go buddy, a function that does exactly what you need!
utils.depthOf = function(object) {
var level = 1;
for(var key in object) {
if (!object.hasOwnProperty(key)) continue;
if(typeof object[key] == 'object'){
var depth = utils.depthOf(object[key]) + 1;
level = Math.max(depth, level);
}
}
return level;
}
A lot easier than we thought it would be. The issue was how it was incremented, it shouldn't have been recursively adding, rather getting the bottom-most and adding one, then choosing the max between two siblings.

This old question was recently resurrected and I don't see any answers as simple as this one (to be fair, this uses techniques not available when the question was written):
const objectDepth = (o) =>
Object (o) === o ? 1 + Math .max (-1, ... Object .values(o) .map (objectDepth)) : 0
console .log (objectDepth ({foo: {bar: {baz: 'baa'}}}))
console .log (objectDepth ({abc: 'xyz'}))
This, like most answers here, will fail when the input object is cyclic. An answer that addresses that limitation would require much more sophistication.

Back from the dead! Throwing my solution into the mix -
function depth (t, mem = new Set)
{ if (mem.has(t))
return Infinity
else switch (mem.add(t), t?.constructor)
{ case Object:
case Array:
return 1 + Math.max
( -1
, ...Object
.values(t)
.map(_ => depth(_, mem))
)
default:
return 0
}
}
console.log(depth({a: {b: {c: "z"}}})) // 3
console.log(depth({a: "z"})) // 1
console.log(depth({})) // 0
console.log(depth("z")) // 0
console.log(depth({a: [{b: "z"}]})) // 3
const a = []
a[0] = a
console.log(depth(a)) // Infinity

We can use the reg:
function getHowManyLevel(obj) {
let res = JSON.stringify(obj).replace(/[^{|^}]/g, '')
while (/}{/g.test(res)) {
res = res.replace(/}{/g, '')
}
return res.replace(/}/g, '').length
}

This should do it, if you wanna keep it short:
function maxDepth(object) {
if (typeof object !== "object" || object === null) {
return 0;
}
let values = Object.values(object);
return (values.length && Math.max(...values.map(value => maxDepth(value)))) + 1;
}

I used a dirty but efficient way :
The good point is that there is no Regex in it, because regex is costly in process time
getObjectDepth = (object) => {
// json to array of parenthesis array: ['{','{','}','}',]
let string = JSON.stringify(object)
.split('')
.filter(char => ['{', '}'].includes(char) );
let currentDepth = 0;
let maxDepth = 0;
string.forEach(char => {
if (char === '{') currentDepth++;
if (char === '}') currentDepth--;
if (currentDepth > maxDepth) maxDepth = currentDepth;
});
return maxDepth
}
It will only work if the object has no parenthesis in a string value though.

Related

Find object by properties from array

With an array, a value, and and an object with nested objects:
Object
mesh
Array
['options', 'range', 'x']
Value
12.5
Is it possible to translate this to update a property, e.g.
mesh.options.range.x = 12.5
Attempted:
index = (obj, i) ->
obj[i]
arr.reduce(index, obj) = 12.5
Update
Thank you all for the elegant solutions.
Using .reduce() is actually pretty nice for this:
// current object----| |----current key
// v v
arr.reduce(function(obj, key) {
return obj == null ? obj : obj[key];
}, window.mesh);
// ^
// |-- initial object
Your attempt to use .reduce() needed to pass a function that manages the "accumulation".
Here, as long as the previous obj wasn't null or undefined, it'll return the key of the current obj, which becomes the next obj.
Then since you need to assign a value, you'd actually want to get the value of the second to last key.
var o = arr.slice(0,-1).reduce(function(obj, key) {
return obj == null ? obj : obj[key];
}, window.mesh);
And then check its existence and use the last item in arr to do the assignment.
o && o[arr.pop()] = 12.5;
All of this can be abstracted away into a function that does one or the other based on how many arguments were passed.
function setFromArray(obj, arr, val) {
var keys = arguments.length < 3 ? arr.slice() : arr.slice(0, -1);
var o = keys.slice(0,-1).reduce(function(obj, key) {
return obj == null ? obj : obj[key];
}, window.mesh);
if (arguments.length < 3)
return o;
else
o && o[keys.pop()];
}
Here's a general solution:
function setPropertyPath(obj, path, value) {
var o = obj;
for (var i = 0; i < path.length - 1; i++) {
o = o[path[i]];
}
o[path[path.length - 1]] = value;
}
Usage:
var obj = { a: { b: { c: 0 } } };
setPropertyPath(obj, ['a', 'b', 'c'], 10);
console.log(obj.a.b.c); // prints '10'
JSBin
var mesh = {},
arr = ['options','range','x'],
value = 12.5;
mesh[arr[0]][arr[1]][arr[2]] = value;
If array length is static do something like this:
mesh[array[0]][array[1]][array[2]] = value;
However, one problem with this is that javascript doesn't do autovivification, so if you're accessing a key value that isn't previously defined you could run into errors (if mesh.options hasn't been defined then the above will throw an error because you can't assign to it). To solve that you might abstract this out into a function that handles things recursively:
http://jsfiddle.net/h4jVg/
function update_val(obj, array, val, prev) {
if (array.length == 0) {
obj = val;
return;
}
var cur = array.shift();
if(array.length == 0) {
obj[cur] = val;
return;
} else if (obj[cur] == undefined) {
obj[cur] = {};
}
update_val(obj[cur], array, val);
}

Sort an array of objects in Javascript by key value

First time poster, long time reader. I’m having a problem sorting an array of objects, this is homework so I’m not asking for someone to write the code for me just point me in the right direction or show me my over sight. The object is to write a function to sort an array of objects when passing in an array and a key ie:
([{a:2},{b:2},{a:1},{a:3},{b:3},{b:1}], “a”)
Should return
[{a:1},{a:2},{a:3},{b:1},{b:2},{b:3}];
I can’t use anything like underscore.js or node.js
//example array to use
var testarr = [{a:2},{b:2},{a:1},{a:3},{b:3},{b:1}];
console.log("array sort test: should look like [{a:1},{a:2},{a:3},{b:1},{b:2},{b:3}]");
//first attempt
var sortArrayByKey = function (arr1, key) {
(arr1.sort(function(a,b){
return a[key] - b[key];}));
return arr1;
};
//still only returns testarr
console.log(sortArrayByKey(testarr, "a") );
//second attempt
var sortArrayByKey1 = function (array, key) {
var compareByKey = function (a, b) {
var x = a[key]; var y = b[key];
return x - y;
}
array.sort(compareByKey);
return array;
};
//still only returns testarr
console.log(sortArrayByKey1(testarr, “a”));
![pic of requirements in-case i'm describing it wrong photo
Here's my solution. I made it so you can also add more keys and sort them too.
Fiddle - http://jsfiddle.net/Q7Q9C/3/
function specialSort(arrayToSort, keyOrder) {
arrayToSort = arrayToSort.sort(function (a, b) {
for (var key in keyOrder) {
if (!keyOrder.hasOwnProperty(key)) {
continue;
}
var aKey = keyOrder[key];
if (typeof a[aKey] === "undefined" && typeof b[aKey] === "undefined") {
continue;
}
if (typeof a[aKey] !== "undefined" && typeof b[aKey] === "undefined") {
return -1;
}
if (typeof a[aKey] === "undefined" && typeof b[aKey] !== "undefined") {
return 1;
}
if (a[aKey] > b[aKey]) {
return 1;
}
else if (a[aKey] < b[aKey]) {
return -1;
}
}
return 0;
});
return arrayToSort;
}
var arrayToSort = [
{a:2},
{b:2},
{a:1},
{a:3},
{b:3},
{c:3},
{c:2},
{b:1}
];
var keyOrder = ["a", "b", "c"];
var sortedArray = specialSort(arrayToSort, keyOrder);
console.log(JSON.stringify(sortedArray));
Hmm.. this is a weird one. First you need to check if one of the keys is the priority key and sort based on that. Then if both keys are equal sort by the values. The problem is that there is no straightforward way to get the key but you can use the for .. in loop.
I'm going to assume that each object contains only one property otherwise the code will not make sense since property is unordered in objects:
function sortPreferredKey(arr,key) {
arr.sort(function(a,b){
// get the keys of each object
for (var a_key in a) {break}
for (var b_key in b) {break}
if (a_key != b_key) {
if (a_key == key) return 1;
else if (b_key == key) return -1;
else return 0;
}
return a[a_key] - b[b_key];
});
}
I may have gotten the order of sort wrong but you get the idea. It's really weird that you'd even need to do something like this.
This is the best I can come up with. It will sort all the elements with the given key to the front; the elements without the key will be in the back, but they'll be in an unpredictable order.
function sortArrayByKey(arr, key) {
function compareKey(a, b) {
if (a.hasOwnProperty(key)) {
if (b.hasOwnProperty(key)) {
return a[key] - b[key];
} else {
return -1;
}
} else if (b.hasOwnProperty(key)) {
return 1;
} else {
return 0;
}
}
arr.sort(compareKey);
return arr;
}
The documentation for the sort method is here. The compare function:
should be a function that accepts two arguments x and y and returns a
negative value if x < y, zero if x = y, or a positive value if x > y.
The function is passed the values in the array, so it's like calling the function with:
compareFunction({a:2},{b:2});
What you seem to want to do is sort on the property name first, then on the value. The problem with that is that you can't guarantee what order the property names are returned in. In this case, if you have exactly one own property for each object, you can do:
// Return first own property returned by in
// ORDER IS NOT GUARANTEED
function getPropName(o) {
for (var p in o) {
if (o.hasOwnProperty(p)) {
return p;
}
}
}
function specialSort(array, key) {
array.sort(function (a, b) {
var aProp = getPropName(a);
var bProp = getPropName(b);
// If properties are the same, compare value
if (aProp == bProp) {
return a[aProp] - b[bProp];
}
// Otherwise, compare keys
return aProp == key? -1 : bProp == key? 1 : aProp.charCodeAt(0) - bProp.charCodeAt(0);
});
return array;
}
The above will also sort any other keys (c, d, e, etc.) after the preferred key so:
var a = [{c:3},{a:2},{b:2},{c:2},{a:1},{a:3},{b:3},{b:1},{c:1}]
specialSort(a, 'b'); // [{b:1}, {b:2}, {b:3}, {a:1}, {a:2}, {a:3}, {c:1}, {c:2}, {c:3}]
Here's a solution that makes a guess what to do if neither object being compared in the array has the passed in comparison key:
var data = [{a:2},{b:2},{a:1},{a:3},{b:3},{b:1}];
function sortByProperty(array, propName) {
function findFirstProperty(obj) {
for (x in obj) {
if (obj.hasOwnProperty(x)) {
return x;
}
}
}
return array.sort(function(first, second) {
var firstHasProp = propName in first;
var secondHasProp = propName in second;
if (firstHasProp) {
if (secondHasProp) {
// both have the property
return first[propName] - second[propName];
} else {
// only first has the property
return -1;
}
} else if (secondHasProp){
// only second has the property
return 1;
} else {
// Neither sort candidate has the passed in property name
// It is not clear what you want to do here as no other property
// name has been specified
return first[findFirstProperty(first)] - second[findFirstProperty(second)]
}
});
}
Working demo: http://jsfiddle.net/jfriend00/PFurT/
Logically, here's what it does:
If both comparison candidates have the desired property, then simply sort by the value of that property.
If only one comparison candidate has the desired property, then make the one with the desired property be first in the sort order
If neither comparison candidate has the desired property, find the first other property on the object and sort by that. This is a guess because you don't really explain what you want to happen in this case, but it works for the data example you provided.
Here's a version that works like the above one, but is has been extended to sort properties that are not the passed in property in alpha order and to deal with empty objects (with no properties) so they go at the end of the sort:
var data = [{c:4},{a:2},{b:2},{a:1},{a:3},{b:3},{b:1},{},{c:3}];
function sortByProperty(array, propName) {
function findFirstProperty(obj) {
for (x in obj) {
if (obj.hasOwnProperty(x)) {
return x;
}
}
}
return array.sort(function(first, second) {
var firstHasProp = propName in first;
var secondHasProp = propName in second;
if (firstHasProp) {
if (secondHasProp) {
// both have the property
return first[propName] - second[propName];
} else {
// only first has the property
return -1;
}
} else if (secondHasProp){
// only second has the property
return 1;
} else {
// Neither sort candidate has the passed in property name
// It is not clear what you want to do here as no other property
// name has been specified
var firstProp = findFirstProperty(first);
var secondProp = findFirstProperty(second);
if (firstProp === undefined && secondProp === undefined) {
return 0;
} else if (firstProp === undefined) {
return 1;
} else if (secondProp === undefined) {
return -1;
}
else if (firstProp === secondProp) {
return first[firstProp] - second[secondProp];
} else {
return firstProp.localeCompare(secondProp);
}
}
});
}
Working demo: http://jsfiddle.net/jfriend00/6QsVv/

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

Why Array.indexOf doesn't find identical looking objects

I have array with objects.
Something Like this:
var arr = new Array(
{x:1, y:2},
{x:3, y:4}
);
When I try:
arr.indexOf({x:1, y:2});
It returns -1.
If I have strings or numbers or other type of elements but object, then indexOf() works fine.
Does anyone know why and what should I do to search object elements in array?
Of course, I mean the ways except making string hash keys for objects and give it to array...
indexOf compares searchElement to elements of the Array using strict equality (the same method used by the ===, or triple-equals, operator).
You cannot use === to check the equability of an object.
As #RobG pointed out
Note that by definition, two objects are never equal, even if they have exactly the same property names and values. objectA === objectB if and only if objectA and objectB reference the same object.
You can simply write a custom indexOf function to check the object.
function myIndexOf(o) {
for (var i = 0; i < arr.length; i++) {
if (arr[i].x == o.x && arr[i].y == o.y) {
return i;
}
}
return -1;
}
DEMO: http://jsfiddle.net/zQtML/
As nobody has mentioned built-in function Array.prototype.findIndex(), I'd like to mention that it does exactly what author needs.
The findIndex() method returns the index of the first element in the
array that satisfies the provided testing function. Otherwise -1 is
returned.
var array1 = [5, 12, 8, 130, 44];
function findFirstLargeNumber(element) {
return element > 13;
}
console.log(array1.findIndex(findFirstLargeNumber));
// expected output: 3
In your case it would be:
arr.findIndex(function(element) {
return element.x == 1 && element.y == 2;
});
Or using ES6
arr.findIndex( element => element.x == 1 && element.y == 2 );
More information with the example above: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex
As noted, two objects are never equal, but references can be equal if they are to the same object, so to make the code do what you want:
var a = {x:1, y:2};
var b = {x:3, y:4};
var arr = [a, b];
alert(arr.indexOf(a)); // 0
Edit
Here's a more general specialIndexOf function. Note that it expects the values of the objects to be primitives, otherwise it needs to be more rigorous.
function specialIndexOf(arr, value) {
var a;
for (var i=0, iLen=arr.length; i<iLen; i++) {
a = arr[i];
if (a === value) return i;
if (typeof a == 'object') {
if (compareObj(arr[i], value)) {
return i;
}
} else {
// deal with other types
}
}
return -1;
// Extremely simple function, expects the values of all
// enumerable properties of both objects to be primitives.
function compareObj(o1, o2, cease) {
var p;
if (typeof o1 == 'object' && typeof o2 == 'object') {
for (p in o1) {
if (o1[p] != o2[p]) return false;
}
if (cease !== true) {
compareObj(o2, o1, true);
}
return true;
}
}
}
var a = new String('fred');
var b = new String('fred');
var arr = [0,1,a];
alert(specialIndexOf(arr, b)); // 2
This works without custom code
var arr, a, found;
arr = [{x: 1, y: 2}];
a = {x: 1, y: 2};
found = JSON.stringify(arr).indexOf(JSON.stringify(a)) > - 1;
// found === true
Note: this does not give the actual index, it only tells if your object exists in the current data structure
Those objects aren't equal.
You must implement your own function.
You may do that for example :
var index = -1;
arr.forEach(function(v, i) {
if (this.x==v.x && this.y==v.y) index=i;
}, searched);
where searched is one of your object (or not).
(I would implement it with a simple loop but it's prettier with foreach)
Because two separate objects are not === to each other, and indexOf uses ===. (They're also not == to each other.)
Example:
var a = {x:1, y:2};
var b = {x:1, y:2};
console.log(a === b);
=== and == test for whether their operands refer to the same object, not if they refer to equivalent objects (objects with the same prototype and properties).
Here's another solution, where you pass a compare function as a parameter :
function indexOf(array, val, from, compare) {
if (!compare) {
if (from instanceof Function) {
compare = from;
from = 0;
}
else return array.__origIndexOf(val, from);
}
if (!from) from = 0;
for (var i=from ; i < array.length ; i++) {
if (compare(array[i], val))
return i;
}
return -1;
}
// Save original indexOf to keep the original behaviour
Array.prototype.__origIndexOf = Array.prototype.indexOf;
// Redefine the Array.indexOf to support a compare function.
Array.prototype.indexOf = function(val, from, compare) {
return indexOf(this, val, from, compare);
}
You can then use it these way:
indexOf(arr, {x:1, y:2}, function (a,b) {
return a.x == b.x && a.y == b.y;
});
arr.indexOf({x:1, y:2}, function (a,b) {
return a.x == b.x && a.y == b.y;
});
arr.indexOf({x:1, y:2}, 1, function (a,b) {
return a.x == b.x && a.y == b.y;
});
The good thing is this still calls the original indexOf if no compare function is passed.
[1,2,3,4].indexOf(3);
Looks like you weren't interested in this type of answer, but it is the simplest to make for others who are interested:
var arr = new Array(
{x:1, y:2},
{x:3, y:4}
);
arr.map(function(obj) {
return objStr(obj);
}).indexOf(objStr({x:1, y:2}));
function objStr(obj) {
return "(" + obj.x + ", " + obj.y + ")"
}

test the existence of property in a deep object structure

In javascript, lets say I want to access a property deep in an object, for example:
entry.mediaGroup[0].contents[0].url
At any point along that structure, a property may be undefined (so mediaGroup may not be set).
What is a simple way to say:
if( entry.mediaGroup[0].contents[0].url ){
console.log( entry.mediaGroup[0].contents[0].url )
}
without generating an error? This way will generate an undefined error if any point along the way is undefined.
My solution
if(entry) && (entry.mediaGroup) && (entry.MediaGroup[0]) ...snip...){
console.log(entry.mediaGroup[0].contents[0].url)
}
which is pretty lengthy. I am guessing there must be something more elegant.
This is a very lazy way to do it, but it meets the criteria for many similar situations:
try {
console.log(entry.mediaGroup[0].contents[0].url);
} catch (e) {}
This should not be done on long code blocks where other errors may potentially be ignored, but should be suitable for a simple situation like this.
/*decend through an object tree to a specified node, and return it.
If node is unreachable, return undefined. This should also work with arrays in the tree.
Examples:
var test1 = {a:{b:{c:{d:1}}}};
console.log(objectDesend(test1, 'a', 'b', 'c', 'd'));
var test2 = {a:{b:{c:1}}}; //will fail to reach d
console.log(objectDesend(test2, 'a', 'b', 'c', 'd'));
*/
var objectDescend = function(){
var obj = arguments[0];
var keys = arguments;
var cur = obj;
for(var i=1; i<keys.length; i++){
var key = keys[i];
var cur = cur[key];
if(typeof(cur)=='undefined')
return cur;
}
return cur;
}
var test1 = {a:{b:{c:{d:1}}}};
console.log(objectDescend(test1, 'a', 'b', 'c', 'd'));
var test2 = {a:{b:{c:1}}};
console.log(objectDescend(test2, 'a', 'b', 'c', 'd'));
So this will return either the value you are looking for, or undefined since that value doesn't exist. It won't return false, as that may actually be the value you are looking for (d:false).
In my code base, I add Object.prototype.descend, so I can do test1.descend('a', 'b', 'c', 'd'). This will only work in ECMAScript 5 (IE>=9) since you need to make it so your function doesn't appear in enumerations. For more info:
Add a method to Object primative, but not have it come up as a property
Here is my code for that:
Object.defineProperty(Object.prototype, 'descend', {
value: function(){
var keys = arguments;
var cur = this;
for(var i=0; i<keys.length; i++){
var key = keys[i];
var cur = cur[key];
if(typeof(cur)=='undefined')
return cur;
}
return cur;
}
});
var test1 = {a:{b:{c:{d:false}}}};
//this will return false, which is the value of d
console.log(test1.descend('a', 'b', 'c', 'd'));
var test2 = {a:{b:{c:1}}};
//undefined since we can't reach d.
console.log(test2.descend(test2, 'a', 'b', 'c', 'd'));
Your current solution is probably as good as you can get, as mVChr says, try..catch is just lazy here. It's probably far less effient and has nothing to recommend it other than perhaps being easier to type (but not significantly so) and it'll be harder to debug as it silently hides errors.
The real issue is the very long "reference worm" created by attempting such access. An alternative to the original that at least reduces the number of property lookups is:
var o;
if ( (o = entry ) &&
(o = o.mediaGroup) &&
(o = o[0] ) &&
(o = o.contents ) &&
(o = o[0] )) {
alert(o.url);
}
But I expect you won't like that.
If you have many such deep access paths, you might like to create a function to do the access and return the last object on success or some other vaule on failure. For failure, you could also have it return the last non-falsey object on the path.
// Create test object
var entry = {};
entry.mediaGroup = [{
contents: [{url: 'url'}]
}];
// Check that it "works"
// alert(entry.mediaGroup[0].contents[0].url);
// Deep property access function, returns last object
// or false
function deepAccess(obj) {
var path = arguments;
var i = 0, iLen = path.length;
var o = path[i++]; // o is first arg
var p = path[i++]; // p is second arg
// Go along path until o[p] is falsey
while (o[p]) {
o = o[p];
p = path[i++];
}
// Return false if didn't get all the way along
// the path or the last non-falsey value referenced
return (--i == iLen) && o;
}
// Test it
var x = deepAccess(entry, 'mediaGroup','0','contents','0');
alert(x && x.url); // url
var x = deepAccess(entry, 'mediaGroup','1','contents','0');
alert(x && x.url); // false
There are probably 3-4 different questions along this vein, and four times as many answers. None of them really satisfied me, so I made my own, and I'll share it.
This function is called "deepGet".
Example:
deepGet(mySampleData, "foo.bar[2].baz", null);
Here is the full code:
function deepGet (obj, path, defaultValue) {
// Split the path into components
var a = path.split('.');
// If we have just one component left, note that for later.
var last = (a.length) === 1;
// See if the next item is an array with an index
var myregexp = /([a-zA-Z]+)(\[(\d+)\])+/; // matches: item[0]
var match = myregexp.exec(a[0]);
// Get the next item
var next;
if (match !== null) {
next = obj[match[1]];
if (next !== undefined) {
next = next[match[3]];
}
} else {
next = obj[a[0]];
}
if (next === undefined || next === null) {
// If we don't have what we want, return the default value
return defaultValue;
} else {
if (last) {
// If it's the last item in the path, return it
return next;
} else {
// If we have more items in the path to go, recurse
return deepGet (next, a.slice(1).join("."), defaultValue);
}
}
}
Here is a jsFiddle: http://jsfiddle.net/7quzmjh8/2/
I was inspired by these two things:
http://designpepper.com/blog/drips/making-deep-property-access-safe-in-javascript.html
http://jsfiddle.net/wxrzM/1/
Hopefully this is useful to someone out there :)
I use this simple function for playing around with deep object properties:
getProperty = function(path) {
try {
return eval(path);
}
catch (e) {
return undefined;
}
};
Here's an example:
var test = {a:{b:{c:"success!"}}};
alert(getProperty('test.c.c'));
// undefined
alert(getProperty('test.a.b.c'));
// success!
Here's the one i have been using for a while
var obj = { a: { b: [
{ c: {d: 'XYZ'} }
] } };
// working
obj.a.b[0].c.d = null;
console.log('value:'+getProperty(obj, 'a.b[0].c.d', 'NOT-AVAILABLE')); // value:null
obj.a.b[0].c.d = 'XYZ';
console.log('value:'+getProperty(obj, 'a.b[0].c.d', 'NOT-AVAILABLE')); // value:XYZ
console.log('value:'+getProperty(obj, 'a.b[0].c.d.k.sds', 'NOT-AVAILABLE')); // value:NOT-AVAILABLE
obj.a.b[0].c = null;
console.log('value:'+getProperty(obj, 'a.b[0].c.d', 'NOT-AVAILABLE')); // value:NOT-AVAILABLE
// will not work
//console.log('v:'+getProperty(obj, 'a.b["0"].c.d'));
Here's the function
function getProperty(obj, str, defaultValue){
var props = str.split('.').map(function(prop){
var arrAccessRegEx = /(.*)\[(.*)\]/g;
if (arrAccessRegEx.test(prop)){
return prop.split(arrAccessRegEx).filter(function(ele){return ele!=''; });
} else {
var retArr = [];
retArr.push(prop);
return retArr
};
});
//console.log(props);
for(var i=0;i<props.length;i++){
var prop = props[i][0];
//console.log('prop:'+prop);
if (obj === null) return defaultValue;
obj = obj[prop];
if (obj === undefined) return defaultValue;
if (props[i].length == 2){
var idx = props[i][1];
if (!(obj instanceof Array)) return defaultValue;
if (idx < obj.length ){
obj = obj[idx];
if (obj === undefined) return defaultValue;
}
}
} // for each item in split
return obj;
}

Categories

Resources