Need to catch any undefined within the entire structure space of the an object literal. The issue is that the location of the undefined will not be predictable:
.object
.result2[0] <--undefined could show its ugly face here, or anywhere above or below!
.thumbnails[0]
.type
.name
.['open']
This doesn't work:
if ( typeof object.result2[0].thumbnails[0]..... type == 'undefined'){
console.log("err'd out")
handleError();
}
So I guess I am looking for a solution that follows: if anything within object is undefined do something, or am I barking up the wrong tree?
If you're doing this sort of thing a lot then a little helper function is useful:
function dig_out(o, path, def) {
var parts = path.split('.');
for(var i = 0; i < parts.length; ++i) {
if(typeof o == 'undefined')
return def;
o = o[parts[i]];
}
return o;
}
obj = { a: [1, [2, { b: 10 } ]]};
var x = dig_out(ob, 'a.1.1.b'); // x is now 10
The trick is to realize that this:
object.results2[0].thumbnails[0].type.name["open"]
can also be written as:
object['results2'][0]['thumbnails'][0]['type']['name']['open']
And that can be easily represented as a string:
'results2.0.thumbnails.0.type.name.open'
that is easy to understand and parse.
You could also represent the path as an array (as CD Sanchez does) but then you'd have to do something with the default value, def.
You could also allow the path argument to be an array:
function dig_out(o, path, def) {
var parts = path instanceof Array ? path : path.split('.');
for(var i = 0; i < parts.length; ++i) {
if(typeof o == 'undefined')
return def;
o = o[parts[i]];
}
return o;
}
obj = { a: [1, [2, { b: 10 } ]]};
var x = dig_out(obj, 'a.1.1.b'); // x is now 10
var y = dig_out(obj, ['a', 1, 1, 'b']); // y is now 10
Then you'd have some flexibility as to which argument format was easiest to work with and it wouldn't even cost that much. Thanks to a small discussion with CD Sanchez for this idea.
Generally you use
if (object && object.result2[0] && object.results2[0].thumbnails[0] && object.results2[0].thumbnails[0].type && object.results2[0].thumbnails[0].type.name) {
object.results2[0].thumbnails[0].type.name["open"]
}
The fact that this looks ugly is a problem with your nesting and that things can be undefined at each level.
Simple try-catch block will do the trick, without need to test each level.
If you really want to keep your code clean, you could make a simple function to check if all the keys exist.
function pathExists() { // untested, but the basis is sound
var obj = arguments[0], path = Array.prototype.slice.call(arguments, 1), cursor = obj;
for (var i = 0; i < path.length; ++i) {
if (typeof cursor[path[i]] == "undefined") return false;
cursor = cursor[path[i]];
}
return cursor;
}
if (!pathExists(object, "result2", 0, "type", "name", "open"))
console.log("bork");
Related
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.
I'm having an issue with trying to populate a multidimensional object in javascript before all of the dimensions are defined.
For example this is what I want to do:
var multiVar = {};
var levelone = 'one';
var leveltwo = 'two';
multiVar[levelone][leveltwo]['levelthree'] = 'test'
It would be extremely cumbersome to have to create each dimension with a line like this:
var multiVar = {};
multiVar['levelone'] = {};
multiVar['levelone']['leveltwo'] = {};
multiVar['levelone']['leveltwo']['levelthree'] = 'test'
The reason why I need to do it without iterative priming is because I don't know how many dimensions there will be nor what the keys it will have. It needs to be dynamic.
Is there a way to do that in a dynamic way?
You could write a function which ensures the existence of the necessary "dimensions", but you won't be able to use dot or bracket notation to get this safety. Something like this:
function setPropertySafe(obj)
{
function isObject(o)
{
if (o === null) return false;
var type = typeof o;
return type === 'object' || type === 'function';
}
if (!isObject(obj)) return;
var prop;
for (var i=1; i < arguments.length-1; i++)
{
prop = arguments[i];
if (!isObject(obj[prop])) obj[prop] = {};
if (i < arguments.length-2) obj = obj[prop];
}
obj[prop] = arguments[i];
}
Example usage:
var multiVar = {};
setPropertySafe(multiVar, 'levelone', 'leveltwo', 'levelthree', 'test');
/*
multiVar = {
levelone: {
leveltwo: {
levelthree: "test"
}
}
}
*/
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;
}
I'm working on an ExtJS webapp and was looking for a way to list all of an object's own property names. Googling around, I quickly found some reference code on this blog. Now, when using this keys() method, I find some strange behavior when enumerating the property names of an object of objects. Example code:
keys = function(obj) {
if (typeof obj != "object" && typeof obj != "function" || obj == null) {
throw TypeError("Object.keys called on non-object");
}
var keys = [];
for (var p in obj)
obj.hasOwnProperty(p) && keys.push(p);
return keys;
};
var test = {}
test["nr1"] = {testid: 1, teststr: "one"};
test["nr2"] = {testid: 2, teststr: "two"};
test["nr3"] = {testid: 3, teststr: "three"};
for (var i in keys(test)) {
console.log(i);
}
When running this code, the console outputs:
0
1
2
remove()
So, on top of the expected 3 property names, it also lists a "remove()" function. This is clearly related to ExtJS, because the enumeration works as expected on a blank, non-ExtJS loading page.
Can anyone explain me what exactly ExtJS is doing here? Is there a better way to enumerate object-own property names?
Thanks a bunch,
wwwald
Try to check hasOwnProperty to only list properties of the array itself, not its prototype.
for (var i in keys(test)) {
if(keys(test).hasOwnProperty(i)){
console.log(i);
}
}
Yes, as #Thai said, not use for..in, as any array is a object and potentially could have different additions in different frameworks.
keys = function(obj) {
if (typeof obj != "object" && typeof obj != "function" || obj == null) {
throw TypeError("Object.keys called on non-object");
}
var keys = [];
for (var p in obj)
obj.hasOwnProperty(p) && keys.push(p);
return keys;
};
var test = {}
test["nr1"] = {testid: 1, teststr: "one"};
test["nr2"] = {testid: 2, teststr: "two"};
test["nr3"] = {testid: 3, teststr: "three"};
document.writeln('<pre>');
document.writeln('Current method');
for (var key in keys(test)) {
document.writeln(key);
}
document.writeln('Better method1');
for (var arr=keys(test), i = 0, iMax = arr.length; i < iMax; i++) {
document.writeln(arr[i]);
}
document.writeln('Better method2');
Ext.each(keys(test), function(key) {
document.writeln(key);
});
document.writeln('</pre>');
keys(test) returns an array, so you are expected to use the classic for-init-condition-next loopm and not the for-in loop.
(function(arr) {
for (var i = 0; i < arr.length; i ++) {
console.log(i);
}
})(keys(test));
This question already has answers here:
Length of a JavaScript object
(43 answers)
Closed 3 years ago.
Is there any built-in function that can return the length of an object?
For example, I have a = { 'a':1,'b':2,'c':3 } which should return 3. If I use a.length it returns undefined.
It could be a simple loop function, but I'd like to know if there's a built-in function?
There is a related question (Length of a JSON object) - in the chosen answer the user advises to transform object into an array, which is not pretty comfortable for my task.
For browsers supporting Object.keys() you can simply do:
Object.keys(a).length;
Otherwise (notably in IE < 9), you can loop through the object yourself with a for (x in y) loop:
var count = 0;
var i;
for (i in a) {
if (a.hasOwnProperty(i)) {
count++;
}
}
The hasOwnProperty is there to make sure that you're only counting properties from the object literal, and not properties it "inherits" from its prototype.
This should do it:
Object.keys(a).length
However, Object.keys is not supported in IE8 and below, Opera and FF 3.6 and below.
Live demo: http://jsfiddle.net/simevidas/nN84h/
Can be done easily with $.map():
var len = $.map(a, function(n, i) { return i; }).length;
Have you taken a look at underscore.js (http://underscorejs.org/docs/underscore.html)? It's a utility library with a lot of useful methods. There is a collection size method, as well as a toArray method, which may get you what you need.
_.size({one : 1, two : 2, three : 3});
=> 3
Summarizing all together, here is a universal function (including ie8 support):
var objSize = function(obj) {
var count = 0;
if (typeof obj == "object") {
if (Object.keys) {
count = Object.keys(obj).length;
} else if (window._) {
count = _.keys(obj).length;
} else if (window.$) {
count = $.map(obj, function() { return 1; }).length;
} else {
for (var key in obj) if (obj.hasOwnProperty(key)) count++;
}
}
return count;
};
document.write(objSize({ a: 1, b: 2, c: 3 }));
// 3
In jQuery i've made it in a such way:
len = function(obj) {
var L=0;
$.each(obj, function(i, elem) {
L++;
});
return L;
}
So one does not have to find and replace the Object.keys method, another approach would be this code early in the execution of the script:
if(!Object.keys)
{
Object.keys = function(obj)
{
return $.map(obj, function(v, k)
{
return k;
});
};
}
Also can be done in this way:
Object.entries(obj).length
For example:
let obj = { a: 1, b: 2, };
console.log(Object.entries(obj).length); //=> 2
// Object.entries(obj) => [ [ 'a', 1 ], [ 'b', 2 ] ]
Here's a jQuery-ised function of Innuendo's answer, ready for use.
$.extend({
keyCount : function(o) {
if(typeof o == "object") {
var i, count = 0;
for(i in o) {
if(o.hasOwnProperty(i)) {
count++;
}
}
return count;
} else {
return false;
}
}
});
Can be called like this:
var cnt = $.keyCount({"foo" : "bar"}); //cnt = 1;
One more answer:
var j = '[{"uid":"1","name":"Bingo Boy", "profile_img":"funtimes.jpg"},{"uid":"2","name":"Johnny Apples", "profile_img":"badtime.jpg"}]';
obj = Object.keys(j).length;
console.log(obj)
For those coming here to find the item count of something that is already a jQuery object:
.length is what you are looking for:
Example:
len = $('#divID').length;
alert(len);
If you want to avoid new dependencies you could make your own smart objects. Of course only if you want to do more that just get it's size.
MyNeatObj = function (obj) {
var length = null;
this.size = function () {
if (length === null) {
length = 0;
for (var key in obj) length++;
}
return length;
}
}
var thingy = new MyNeatObj(originalObj);
thingy.size();
You might have an undefined property in the object.
If using the method of Object.keys(data).length is used those properties will also be counted.
You might want to filter them out out.
Object.keys(data).filter((v) => {return data[v] !== undefined}).length
You may use something like Lodash lib and _.toLength(object) should give you the length of your object
You could add another name:value pair of length, and increment/decrement it appropriately. This way, when you need to query the length, you don't have to iterate through the entire objects properties every time, and you don't have to rely on a specific browser or library. It all depends on your goal, of course.