Access Javascript nested objects safely - javascript

I have json based data structure with objects containing nested objects. In order to access a particular data element I have been chaining references to object properties together. For example:
var a = b.c.d;
If b or b.c is undefined, this will fail with an error. However, I want to get a value if it exists otherwise just undefined. What is the best way to do this without having to check that every value in the chain exists?
I would like to keep this method as general as possible so I don't have to add huge numbers of helper methods like:
var a = b.getD();
or
var a = helpers.getDFromB(b);
I also want to try to avoid try/catch constructs as this isn't an error so using try/catch seems misplaced. Is that reasonable?
Any ideas?

ECMAScript2020, and in Node v14, has the optional chaining operator (I've seen it also called safe navigation operator), which would allow your example to be written as:
var a = b?.c?.d;
From the MDN docs:
The optional chaining operator (?.) permits reading the value of a property located deep within a chain of connected objects without having to expressly validate that each reference in the chain is valid. The ?. operator functions similarly to the . chaining operator, except that instead of causing an error if a reference is nullish (null or undefined), the expression short-circuits with a return value of undefined. When used with function calls, it returns undefined if the given function does not exist.

Standard approach:
var a = b && b.c && b.c.d && b.c.d.e;
is quite fast but not too elegant (especially with longer property names).
Using functions to traverse JavaScipt object properties is neither efficient nor elegant.
Try this instead:
try { var a = b.c.d.e; } catch(e){}
in case you are certain that a was not previously used or
try { var a = b.c.d.e; } catch(e){ a = undefined; }
in case you may have assigned it before.
This is probably even faster that the first option.

ES6 has optional chaining which can be used as follows:
const object = { foo: {bar: 'baz'} };
// not found, undefined
console.log(object?.foo?.['nested']?.missing?.prop)
// not found, object as default value
console.log(object?.foo?.['nested']?.missing?.prop || {})
// found, "baz"
console.log(object?.foo?.bar)
This approach requires the variable "object" to be defined and to be an object.
Alternatively, you could define your own utility, here's an example which implements recursion:
const traverseObject = (object, propertyName, defaultValue) => {
if (Array.isArray(propertyName)) {
return propertyName.reduce((o, p) => traverseObject(o, p, defaultValue), object);
}
const objectSafe = object || {};
return objectSafe[propertyName] || defaultValue;
};
// not found, undefined
console.log(traverseObject({}, 'foo'));
// not found, object as default value
console.log(traverseObject(null, ['foo', 'bar'], {}));
// found "baz"
console.log(traverseObject({foo: {bar:'baz'}}, ['foo','bar']));

You can create a general method that access an element based on an array of property names that is interpreted as a path through the properties:
function getValue(data, path) {
var i, len = path.length;
for (i = 0; typeof data === 'object' && i < len; ++i) {
data = data[path[i]];
}
return data;
}
Then you could call it with:
var a = getValue(b, ["c", "d"]);

This is an old question and now with es6 features, this problem can be solved more easily.
const idx = (p, o) => p.reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, o);
Thanks to #sharifsbeat for this solution.

The answers here are good bare-metal solutions. However, if you just want to use a package that is tried and true, I recommend using lodash.
With ES6 you can run the following
import _ from 'lodash'
var myDeepObject = {...}
value = _.get(myDeepObject, 'maybe.these.path.exist', 'Default value if not exists')

probably it's may be simple:
let a = { a1: 11, b1: 12, c1: { d1: 13, e1: { g1: 14 }}}
console.log((a || {}).a2); => undefined
console.log(((a || {}).c1 || {}).d1) => 13
and so on.

const getValue = (obj, property, defaultValue) => (
property.split('.').reduce((item, key) => {
if (item && typeof item === 'object' && key in item) {
return item[key];
}
return defaultValue;
}, obj)
)
const object = { 'a': { 'b': { 'c': 3 } } };
getValue(object, 'a.b.c'); // 3
getValue(object, 'a.b.x'); // undefined
getValue(object, 'a.b.x', 'default'); // 'default'
getValue(object, 'a.x.c'); // undefined

I will just paste the function that I use in almost all project as utility for this type of situation.
public static is(fn: Function, dv: any) {
try {
if (fn()) {
return fn()
} else {
return dv
}
} catch (e) {
return dv
}
}
So first argument is callback and second is the default value if it fails to extract the data due to some error.
I call it at all places as follows:
var a = is(()=> a.b.c, null);

// The code for the regex isn't great,
// but it suffices for most use cases.
/**
* Gets the value at `path` of `object`.
* If the resolved value is `undefined`,
* or the property does not exist (set param has: true),
* the `defaultValue` is returned in its place.
*
* #param {Object} object The object to query.
* #param {Array|string} path The path of the property to get.
* #param {*} [def] The value returned for `undefined` resolved values.
* #param {boolean} [has] Return property instead of default value if key exists.
* #returns {*} Returns the resolved value.
* #example
*
* var object = { 'a': [{ 'b': { 'c': 3 } }], b: {'c-[d.e]': 1}, c: { d: undefined, e: 0 } };
*
* dotGet(object, 'a[0].b.c');
* // => 3
*
* dotGet(object, ['a', '0', 'b', 'c']);
* // => 3
*
* dotGet(object, ['b', 'c-[d.e]']);
* // => 1
*
* dotGet(object, 'c.d', 'default value');
* // => 'default value'
*
* dotGet(object, 'c.d', 'default value', true);
* // => undefined
*
* dotGet(object, 'c.d.e', 'default value');
* // => 'default value'
*
* dotGet(object, 'c.d.e', 'default value', true);
* // => 'default value'
*
* dotGet(object, 'c.e') || 5; // non-true default value
* // => 5
*
*/
var dotGet = function (obj, path, def, has) {
return (typeof path === 'string' ? path.split(/[\.\[\]\'\"]/) : path)
.filter(function (p) { return 0 === p ? true : p; })
.reduce(function (o, p) {
return typeof o === 'object' ? ((
has ? o.hasOwnProperty(p) : o[p] !== undefined
) ? o[p] : def) : def;
}, obj);
}

If you would like to have a dynamic access with irregular number of properties at hand, in ES6 you might easily do as follows;
function getNestedValue(o,...a){
var val = o;
for (var prop of a) val = typeof val === "object" &&
val !== null &&
val[prop] !== void 0 ? val[prop]
: undefined;
return val;
}
var obj = {a:{foo:{bar:null}}};
console.log(getNestedValue(obj,"a","foo","bar"));
console.log(getNestedValue(obj,"a","hop","baz"));

Gets the value at path of object. If the resolved value is undefined, the defaultValue is returned in its place.
In ES6 we can get nested property from an Object like below code snippet.
const myObject = {
a: {
b: {
c: {
d: 'test'
}
}
},
c: {
d: 'Test 2'
}
},
isObject = obj => obj && typeof obj === 'object',
hasKey = (obj, key) => key in obj;
function nestedObj(obj, property, callback) {
return property.split('.').reduce((item, key) => {
if (isObject(item) && hasKey(item, key)) {
return item[key];
}
return typeof callback != undefined ? callback : undefined;
}, obj);
}
console.log(nestedObj(myObject, 'a.b.c.d')); //return test
console.log(nestedObj(myObject, 'a.b.c.d.e')); //return undefined
console.log(nestedObj(myObject, 'c.d')); //return Test 2
console.log(nestedObj(myObject, 'd.d', false)); //return false
console.log(nestedObj(myObject, 'a.b')); //return {"c": {"d": "test"}}

An old question, and now days we have Typescript projects so often that this question seems irrelevant, but I got here searching for the same thing, so I made a simple function to do it. Your thoughts about not using try/catch is too strict for my taste, after all the seek for undefined.x will cause an error anyway.
So with all that, this is my method.
function getSafe (obj, valuePath) {
try { return eval("obj." + valuePath); }
catch (err) { return null; }
}
To use this we have to pass the object. I tried to avoid that, but there was not other way to get scope into it from another function (there is a whole bunch of questions about this in here).
And a small test set to see what we get:
let outsideObject = {
html: {
pageOne: {
pageTitle: 'Lorem Ipsum!'
}
}
};
function testme() {
let insideObject = { a: { b: 22 } };
return {
b: getSafe(insideObject, "a.b"), // gives: 22
e: getSafe(insideObject, "a.b.c.d.e"), // gives: null
pageTitle: getSafe(outsideObject, "html.pageOne.pageTitle"), // gives: Lorem Ipsum!
notThere: getSafe(outsideObject, "html.pageOne.pageTitle.style") // gives: undefined
}
}
testme();
UPDATE:
Regarding the use of eval I think that eval is a tool to use carefully and not the devil itself. In this method, the user does not interfere with eval since it is the developer that is looking for a property by its name.

If you care about syntax, here's a cleaner version of Hosar's answer:
function safeAccess(path, object) {
if (object) {
return path.reduce(
(accumulator, currentValue) => (accumulator && accumulator[currentValue] ? accumulator[currentValue] : null),
object,
);
} else {
return null;
}
}

Related

Display value from object in javascript [duplicate]

Given a JavaScript object,
var obj = { a: { b: '1', c: '2' } }
and a string
"a.b"
how can I convert the string to dot notation so I can go
var val = obj.a.b
If the string was just 'a', I could use obj[a]. But this is more complex. I imagine there is some straightforward method, but it escapes me at present.
recent note: While I'm flattered that this answer has gotten many upvotes, I am also somewhat horrified. If one needs to convert dot-notation strings like "x.a.b.c" into references, it could (maybe) be a sign that there is something very wrong going on (unless maybe you're performing some strange deserialization).
That is to say, novices who find their way to this answer must ask themselves the question "why am I doing this?"
It is of course generally fine to do this if your use case is small and you will not run into performance issues, AND you won't need to build upon your abstraction to make it more complicated later. In fact, if this will reduce code complexity and keep things simple, you should probably go ahead and do what OP is asking for. However, if that's not the case, consider if any of these apply:
case 1: As the primary method of working with your data (e.g. as your app's default form of passing objects around and dereferencing them). Like asking "how can I look up a function or variable name from a string".
This is bad programming practice (unnecessary metaprogramming specifically, and kind of violates function side-effect-free coding style, and will have performance hits). Novices who find themselves in this case, should instead consider working with array representations, e.g. ['x','a','b','c'], or even something more direct/simple/straightforward if possible: like not losing track of the references themselves in the first place (most ideal if it's only client-side or only server-side), etc. (A pre-existing unique id would be inelegant to add, but could be used if the spec otherwise requires its existence regardless.)
case 2: Working with serialized data, or data that will be displayed to the user. Like using a date as a string "1999-12-30" rather than a Date object (which can cause timezone bugs or added serialization complexity if not careful). Or you know what you're doing.
This is maybe fine. Be careful that there are no dot strings "." in your sanitized input fragments.
If you find yourself using this answer all the time and converting back and forth between string and array, you may be in the bad case, and should consider an alternative.
Here's an elegant one-liner that's 10x shorter than the other solutions:
function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)
[edit] Or in ECMAScript 6:
'a.b.etc'.split('.').reduce((o,i)=> o[i], obj)
(Not that I think eval always bad like others suggest it is (though it usually is), nevertheless those people will be pleased that this method doesn't use eval. The above will find obj.a.b.etc given obj and the string "a.b.etc".)
In response to those who still are afraid of using reduce despite it being in the ECMA-262 standard (5th edition), here is a two-line recursive implementation:
function multiIndex(obj,is) { // obj,['1','2','3'] -> ((obj['1'])['2'])['3']
return is.length ? multiIndex(obj[is[0]],is.slice(1)) : obj
}
function pathIndex(obj,is) { // obj,'1.2.3' -> multiIndex(obj,['1','2','3'])
return multiIndex(obj,is.split('.'))
}
pathIndex('a.b.etc')
Depending on the optimizations the JS compiler is doing, you may want to make sure any nested functions are not re-defined on every call via the usual methods (placing them in a closure, object, or global namespace).
edit:
To answer an interesting question in the comments:
how would you turn this into a setter as well? Not only returning the values by path, but also setting them if a new value is sent into the function? – Swader Jun 28 at 21:42
(sidenote: sadly can't return an object with a Setter, as that would violate the calling convention; commenter seems to instead be referring to a general setter-style function with side-effects like index(obj,"a.b.etc", value) doing obj.a.b.etc = value.)
The reduce style is not really suitable to that, but we can modify the recursive implementation:
function index(obj,is, value) {
if (typeof is == 'string')
return index(obj,is.split('.'), value);
else if (is.length==1 && value!==undefined)
return obj[is[0]] = value;
else if (is.length==0)
return obj;
else
return index(obj[is[0]],is.slice(1), value);
}
Demo:
> obj = {a:{b:{etc:5}}}
> index(obj,'a.b.etc')
5
> index(obj,['a','b','etc']) #works with both strings and lists
5
> index(obj,'a.b.etc', 123) #setter-mode - third argument (possibly poor form)
123
> index(obj,'a.b.etc')
123
...though personally I'd recommend making a separate function setIndex(...). I would like to end on a side-note that the original poser of the question could (should?) be working with arrays of indices (which they can get from .split), rather than strings; though there's usually nothing wrong with a convenience function.
A commenter asked:
what about arrays? something like "a.b[4].c.d[1][2][3]" ? –AlexS
Javascript is a very weird language; in general objects can only have strings as their property keys, so for example if x was a generic object like x={}, then x[1] would become x["1"]... you read that right... yup...
Javascript Arrays (which are themselves instances of Object) specifically encourage integer keys, even though you could do something like x=[]; x["puppy"]=5;.
But in general (and there are exceptions), x["somestring"]===x.somestring (when it's allowed; you can't do x.123).
(Keep in mind that whatever JS compiler you're using might choose, maybe, to compile these down to saner representations if it can prove it would not violate the spec.)
So the answer to your question would depend on whether you're assuming those objects only accept integers (due to a restriction in your problem domain), or not. Let's assume not. Then a valid expression is a concatenation of a base identifier plus some .identifiers plus some ["stringindex"]s.
Let us ignore for a moment that we can of course do other things legitimately in the grammar like identifier[0xFA7C25DD].asdf[f(4)?.[5]+k][false][null][undefined][NaN]; integers are not (that) 'special'.
Commenter's statement would then be equivalent to a["b"][4]["c"]["d"][1][2][3], though we should probably also support a.b["c\"validjsstringliteral"][3]. You'd have to check the ecmascript grammar section on string literals to see how to parse a valid string literal. Technically you'd also want to check (unlike in my first answer) that a is a valid javascript identifier.
A simple answer to your question though, if your strings don't contain commas or brackets, would be just be to match length 1+ sequences of characters not in the set , or [ or ]:
> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^ ^ ^^^ ^ ^ ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]
If your strings don't contain escape characters or " characters, and because IdentifierNames are a sublanguage of StringLiterals (I think???) you could first convert your dots to []:
> var R=[], demoString="abc[4].c.def[1][2][\"gh\"]";
> for(var match,matcher=/^([^\.\[]+)|\.([^\.\[]+)|\["([^"]+)"\]|\[(\d+)\]/g;
match=matcher.exec(demoString); ) {
R.push(Array.from(match).slice(1).filter(x=> x!==undefined)[0]);
// extremely bad code because js regexes are weird, don't use this
}
> R
["abc", "4", "c", "def", "1", "2", "gh"]
Of course, always be careful and never trust your data. Some bad ways to do this that might work for some use cases also include:
// hackish/wrongish; preprocess your string into "a.b.4.c.d.1.2.3", e.g.:
> yourstring.replace(/]/g,"").replace(/\[/g,".").split(".")
"a.b.4.c.d.1.2.3" //use code from before
Special 2018 edit:
Let's go full-circle and do the most inefficient, horribly-overmetaprogrammed solution we can come up with... in the interest of syntactical purityhamfistery. With ES6 Proxy objects!... Let's also define some properties which (imho are fine and wonderful but) may break improperly-written libraries. You should perhaps be wary of using this if you care about performance, sanity (yours or others'), your job, etc.
// [1,2,3][-1]==3 (or just use .slice(-1)[0])
if (![1][-1])
Object.defineProperty(Array.prototype, -1, {get() {return this[this.length-1]}}); //credit to caub
// WARNING: THIS XTREME™ RADICAL METHOD IS VERY INEFFICIENT,
// ESPECIALLY IF INDEXING INTO MULTIPLE OBJECTS,
// because you are constantly creating wrapper objects on-the-fly and,
// even worse, going through Proxy i.e. runtime ~reflection, which prevents
// compiler optimization
// Proxy handler to override obj[*]/obj.* and obj[*]=...
var hyperIndexProxyHandler = {
get: function(obj,key, proxy) {
return key.split('.').reduce((o,i)=> o[i], obj);
},
set: function(obj,key,value, proxy) {
var keys = key.split('.');
var beforeLast = keys.slice(0,-1).reduce((o,i)=> o[i], obj);
beforeLast[keys[-1]] = value;
},
has: function(obj,key) {
//etc
}
};
function hyperIndexOf(target) {
return new Proxy(target, hyperIndexProxyHandler);
}
Demo:
var obj = {a:{b:{c:1, d:2}}};
console.log("obj is:", JSON.stringify(obj));
var objHyper = hyperIndexOf(obj);
console.log("(proxy override get) objHyper['a.b.c'] is:", objHyper['a.b.c']);
objHyper['a.b.c'] = 3;
console.log("(proxy override set) objHyper['a.b.c']=3, now obj is:", JSON.stringify(obj));
console.log("(behind the scenes) objHyper is:", objHyper);
if (!({}).H)
Object.defineProperties(Object.prototype, {
H: {
get: function() {
return hyperIndexOf(this); // TODO:cache as a non-enumerable property for efficiency?
}
}
});
console.log("(shortcut) obj.H['a.b.c']=4");
obj.H['a.b.c'] = 4;
console.log("(shortcut) obj.H['a.b.c'] is obj['a']['b']['c'] is", obj.H['a.b.c']);
Output:
obj is: {"a":{"b":{"c":1,"d":2}}}
(proxy override get) objHyper['a.b.c'] is: 1
(proxy override set) objHyper['a.b.c']=3, now obj is: {"a":{"b":{"c":3,"d":2}}}
(behind the scenes) objHyper is: Proxy {a: {…}}
(shortcut) obj.H['a.b.c']=4
(shortcut) obj.H['a.b.c'] is obj['a']['b']['c'] is: 4
inefficient idea: You can modify the above to dispatch based on the input argument; either use the .match(/[^\]\[.]+/g) method to support obj['keys'].like[3]['this'], or if instanceof Array, then just accept an Array as input like keys = ['a','b','c']; obj.H[keys].
Per suggestion that maybe you want to handle undefined indices in a 'softer' NaN-style manner (e.g. index({a:{b:{c:...}}}, 'a.x.c') return undefined rather than uncaught TypeError)...:
This makes sense from the perspective of "we should return undefined rather than throw an error" in the 1-dimensional index situation ({})['e.g.']==undefined, so "we should return undefined rather than throw an error" in the N-dimensional situation.
This does not make sense from the perspective that we are doing x['a']['x']['c'], which would fail with a TypeError in the above example.
That said, you'd make this work by replacing your reducing function with either:
(o,i)=> o===undefined?undefined:o[i], or
(o,i)=> (o||{})[i].
(You can make this more efficient by using a for loop and breaking/returning whenever the subresult you'd next index into is undefined, or using a try-catch if you expect such failures to be sufficiently rare.)
If you can use Lodash, there is a function, which does exactly that:
_.get(object, path, [defaultValue])
var val = _.get(obj, "a.b");
You could use lodash.get
After installing (npm i lodash.get), use it like this:
const get = require('lodash.get');
const myObj = {
user: {
firstName: 'Stacky',
lastName: 'Overflowy',
list: ['zero', 'one', 'two']
},
id: 123
};
console.log(get(myObj, 'user.firstName')); // outputs Stacky
console.log(get(myObj, 'id')); // outputs 123
console.log(get(myObj, 'user.list[1]')); // outputs one
// You can also update values
get(myObj, 'user').firstName = 'John';
A little more involved example with recursion.
function recompose(obj, string) {
var parts = string.split('.');
var newObj = obj[parts[0]];
if (parts[1]) {
parts.splice(0, 1);
var newString = parts.join('.');
return recompose(newObj, newString);
}
return newObj;
}
var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};
console.log(recompose(obj, 'a.d.a.b')); //blah
2021
You don't need to pull in another dependency every time you wish for new capabilities in your program. Modern JS is very capable and the optional-chaining operator ?. is now widely supported and makes this kind of task easy as heck.
With a single line of code we can write get that takes an input object, t and string path. It works for object and arrays of any nesting level -
const get = (t, path) =>
path.split(".").reduce((r, k) => r?.[k], t)
const mydata =
{ a: { b: [ 0, { c: { d: [ "hello", "world" ] } } ] } }
console.log(get(mydata, "a.b.1.c.d.0"))
console.log(get(mydata, "a.b.1.c.d.1"))
console.log(get(mydata, "a.b.x.y.z"))
"hello"
"world"
undefined
I suggest to split the path and iterate it and reduce the object you have. This proposal works with a default value for missing properties.
const getValue = (object, keys) => keys.split('.').reduce((o, k) => (o || {})[k], object);
console.log(getValue({ a: { b: '1', c: '2' } }, 'a.b'));
console.log(getValue({ a: { b: '1', c: '2' } }, 'foo.bar.baz'));
Many years since the original post.
Now there is a great library called 'object-path'.
https://github.com/mariocasciaro/object-path
Available on NPM and BOWER
https://www.npmjs.com/package/object-path
It's as easy as:
objectPath.get(obj, "a.c.1"); //returns "f"
objectPath.set(obj, "a.j.0.f", "m");
And works for deeply nested properties and arrays.
If you expect to dereference the same path many times, building a function for each dot notation path actually has the best performance by far (expanding on the perf tests James Wilkins linked to in comments above).
var path = 'a.b.x';
var getter = new Function("obj", "return obj." + path + ";");
getter(obj);
Using the Function constructor has some of the same drawbacks as eval() in terms of security and worst-case performance, but IMO it's a badly underused tool for cases where you need a combination of extreme dynamism and high performance. I use this methodology to build array filter functions and call them inside an AngularJS digest loop. My profiles consistently show the array.filter() step taking less than 1ms to dereference and filter about 2000 complex objects, using dynamically-defined paths 3-4 levels deep.
A similar methodology could be used to create setter functions, of course:
var setter = new Function("obj", "newval", "obj." + path + " = newval;");
setter(obj, "some new val");
Other proposals are a little cryptic, so I thought I'd contribute:
Object.prop = function(obj, prop, val){
var props = prop.split('.')
, final = props.pop(), p
while(p = props.shift()){
if (typeof obj[p] === 'undefined')
return undefined;
obj = obj[p]
}
return val ? (obj[final] = val) : obj[final]
}
var obj = { a: { b: '1', c: '2' } }
// get
console.log(Object.prop(obj, 'a.c')) // -> 2
// set
Object.prop(obj, 'a.c', function(){})
console.log(obj) // -> { a: { b: '1', c: [Function] } }
var a = { b: { c: 9 } };
function value(layer, path, value) {
var i = 0,
path = path.split('.');
for (; i < path.length; i++)
if (value != null && i + 1 === path.length)
layer[path[i]] = value;
layer = layer[path[i]];
return layer;
};
value(a, 'b.c'); // 9
value(a, 'b.c', 4);
value(a, 'b.c'); // 4
This is a lot of code when compared to the much simpler eval way of doing it, but like Simon Willison says, you should never use eval.
Also, JSFiddle.
You can use the library available at npm, which simplifies this process. https://www.npmjs.com/package/dot-object
var dot = require('dot-object');
var obj = {
some: {
nested: {
value: 'Hi there!'
}
}
};
var val = dot.pick('some.nested.value', obj);
console.log(val);
// Result: Hi there!
Note if you're already using Lodash you can use the property or get functions:
var obj = { a: { b: '1', c: '2' } };
_.property('a.b')(obj); // => 1
_.get(obj, 'a.b'); // => 1
Underscore.js also has a property function, but it doesn't support dot notation.
I have extended the elegant answer by ninjagecko so that the function handles both dotted and/or array style references, and so that an empty string causes the parent object to be returned.
Here you go:
string_to_ref = function (object, reference) {
function arr_deref(o, ref, i) { return !ref ? o : (o[ref.slice(0, i ? -1 : ref.length)]) }
function dot_deref(o, ref) { return ref.split('[').reduce(arr_deref, o); }
return !reference ? object : reference.split('.').reduce(dot_deref, object);
};
See my working jsFiddle example here: http://jsfiddle.net/sc0ttyd/q7zyd/
You can obtain value of an object member by dot notation with a single line of code:
new Function('_', 'return _.' + path)(obj);
In you case:
var obj = { a: { b: '1', c: '2' } }
var val = new Function('_', 'return _.a.b')(obj);
To make it simple you may write a function like this:
function objGet(obj, path){
return new Function('_', 'return _.' + path)(obj);
}
Explanation:
The Function constructor creates a new Function object. In JavaScript every function is actually a Function object. Syntax to create a function explicitly with Function constructor is:
new Function ([arg1[, arg2[, ...argN]],] functionBody)
where arguments(arg1 to argN) must be a string that corresponds to a valid javaScript identifier and functionBody is a string containing the javaScript statements comprising the function definition.
In our case we take the advantage of string function body to retrieve object member with dot notation.
Hope it helps.
var find = function(root, path) {
var segments = path.split('.'),
cursor = root,
target;
for (var i = 0; i < segments.length; ++i) {
target = cursor[segments[i]];
if (typeof target == "undefined") return void 0;
cursor = target;
}
return cursor;
};
var obj = { a: { b: '1', c: '2' } }
find(obj, "a.b"); // 1
var set = function (root, path, value) {
var segments = path.split('.'),
cursor = root,
target;
for (var i = 0; i < segments.length - 1; ++i) {
cursor = cursor[segments[i]] || { };
}
cursor[segments[segments.length - 1]] = value;
};
set(obj, "a.k", function () { console.log("hello world"); });
find(obj, "a.k")(); // hello world
Use this function:
function dotToObject(data) {
function index(parent, key, value) {
const [mainKey, ...children] = key.split(".");
parent[mainKey] = parent[mainKey] || {};
if (children.length === 1) {
parent[mainKey][children[0]] = value;
} else {
index(parent[mainKey], children.join("."), value);
}
}
const result = Object.entries(data).reduce((acc, [key, value]) => {
if (key.includes(".")) {
index(acc, key, value);
} else {
acc[key] = value;
}
return acc;
}, {});
return result;
}
module.exports = { dotToObject };
Ex:
const user = {
id: 1,
name: 'My name',
'address.zipCode': '123',
'address.name': 'Some name',
'address.something.id': 1,
}
const mappedUser = dotToObject(user)
console.log(JSON.stringify(mappedUser, null, 2))
Output:
{
"id": 1,
"name": "My name",
"address": {
"zipCode": "123",
"name": "Some name",
"something": {
"id": 1
}
}
}
using Array Reduce function will get/set based on path provided.
I tested it with a.b.c and a.b.2.c {a:{b:[0,1,{c:7}]}} and its works for both getting key or mutating object to set value
function setOrGet(obj, path=[], newValue){
const l = typeof path === 'string' ? path.split('.') : path;
return l.reduce((carry,item, idx)=>{
const leaf = carry[item];
// is this last item in path ? cool lets set/get value
if( l.length-idx===1) {
// mutate object if newValue is set;
carry[item] = newValue===undefined ? leaf : newValue;
// return value if its a get/object if it was a set
return newValue===undefined ? leaf : obj ;
}
carry[item] = leaf || {}; // mutate if key not an object;
return carry[item]; // return object ref: to continue reduction;
}, obj)
}
console.log(
setOrGet({a: {b:1}},'a.b') === 1 ||
'Test Case: Direct read failed'
)
console.log(
setOrGet({a: {b:1}},'a.c',22).a.c===22 ||
'Test Case: Direct set failed'
)
console.log(
setOrGet({a: {b:[1,2]}},'a.b.1',22).a.b[1]===22 ||
'Test Case: Direct set on array failed'
)
console.log(
setOrGet({a: {b:{c: {e:1} }}},'a.b.c.e',22).a.b.c. e===22 ||
'Test Case: deep get failed'
)
// failed !. Thats your homework :)
console.log(
setOrGet({a: {b:{c: {e:[1,2,3,4,5]} }}},'a.b.c.e.3 ',22)
)
my personal recommendation.
do not use such a thing unless there is no other way!
i saw many examples people use it for translations for example from json; so you see function like locale('app.homepage.welcome') . this is just bad. if you already have data in an object/json; and you know path.. then just use it directly example locale().app.homepage.welcome by changing you function to return object you get typesafe, with autocomplete, less prone to typo's ..
I copied the following from Ricardo Tomasi's answer and modified to also create sub-objects that don't yet exist as necessary. It's a little less efficient (more ifs and creating of empty objects), but should be pretty good.
Also, it'll allow us to do Object.prop(obj, 'a.b', false) where we couldn't before. Unfortunately, it still won't let us assign undefined...Not sure how to go about that one yet.
/**
* Object.prop()
*
* Allows dot-notation access to object properties for both getting and setting.
*
* #param {Object} obj The object we're getting from or setting
* #param {string} prop The dot-notated string defining the property location
* #param {mixed} val For setting only; the value to set
*/
Object.prop = function(obj, prop, val){
var props = prop.split('.'),
final = props.pop(),
p;
for (var i = 0; i < props.length; i++) {
p = props[i];
if (typeof obj[p] === 'undefined') {
// If we're setting
if (typeof val !== 'undefined') {
// If we're not at the end of the props, keep adding new empty objects
if (i != props.length)
obj[p] = {};
}
else
return undefined;
}
obj = obj[p]
}
return typeof val !== "undefined" ? (obj[final] = val) : obj[final]
}
Few years later, I found this that handles scope and array. e.g. a['b']["c"].d.etc
function getScopedObj(scope, str) {
let obj=scope, arr;
try {
arr = str.split(/[\[\]\.]/) // split by [,],.
.filter(el => el) // filter out empty one
.map(el => el.replace(/^['"]+|['"]+$/g, '')); // remove string quotation
arr.forEach(el => obj = obj[el])
} catch(e) {
obj = undefined;
}
return obj;
}
window.a = {b: {c: {d: {etc: 'success'}}}}
getScopedObj(window, `a.b.c.d.etc`) // success
getScopedObj(window, `a['b']["c"].d.etc`) // success
getScopedObj(window, `a['INVALID']["c"].d.etc`) // undefined
If you wish to convert any object that contains dot notation keys into an arrayed version of those keys you can use this.
This will convert something like
{
name: 'Andy',
brothers.0: 'Bob'
brothers.1: 'Steve'
brothers.2: 'Jack'
sisters.0: 'Sally'
}
to
{
name: 'Andy',
brothers: ['Bob', 'Steve', 'Jack']
sisters: ['Sally']
}
convertDotNotationToArray(objectWithDotNotation) {
Object.entries(objectWithDotNotation).forEach(([key, val]) => {
// Is the key of dot notation
if (key.includes('.')) {
const [name, index] = key.split('.');
// If you have not created an array version, create one
if (!objectWithDotNotation[name]) {
objectWithDotNotation[name] = new Array();
}
// Save the value in the newly created array at the specific index
objectWithDotNotation[name][index] = val;
// Delete the current dot notation key val
delete objectWithDotNotation[key];
}
});
}
If you want to convert a string dot notation into an object, I've made a handy little helper than can turn a string like a.b.c.d with a value of e with dotPathToObject("a.b.c.d", "value") returning this:
{
"a": {
"b": {
"c": {
"d": "value"
}
}
}
}
https://gist.github.com/ahallora/9731d73efb15bd3d3db647efa3389c12
Solution:
function deepFind(key, data){
return key.split('.').reduce((ob,i)=> ob?.[i], data)
}
Usage:
const obj = {
company: "Pet Shop",
person: {
name: "John"
},
animal: {
name: "Lucky"
}
}
const company = deepFind("company", obj)
const personName = deepFind("person.name", obj)
const animalName = deepFind("animal.name", obj)
Here is my implementation
Implementation 1
Object.prototype.access = function() {
var ele = this[arguments[0]];
if(arguments.length === 1) return ele;
return ele.access.apply(ele, [].slice.call(arguments, 1));
}
Implementation 2 (using array reduce instead of slice)
Object.prototype.access = function() {
var self = this;
return [].reduce.call(arguments,function(prev,cur) {
return prev[cur];
}, self);
}
Examples:
var myobj = {'a':{'b':{'c':{'d':'abcd','e':[11,22,33]}}}};
myobj.access('a','b','c'); // returns: {'d':'abcd', e:[0,1,2,3]}
myobj.a.b.access('c','d'); // returns: 'abcd'
myobj.access('a','b','c','e',0); // returns: 11
it can also handle objects inside arrays as for
var myobj2 = {'a': {'b':[{'c':'ab0c'},{'d':'ab1d'}]}}
myobj2.access('a','b','1','d'); // returns: 'ab1d'
I used this code in my project
const getValue = (obj, arrPath) => (
arrPath.reduce((x, y) => {
if (y in x) return x[y]
return {}
}, obj)
)
Usage:
const obj = { id: { user: { local: 104 } } }
const path = [ 'id', 'user', 'local' ]
getValue(obj, path) // return 104
Using object-scan seems a bit overkill, but you can simply do
// const objectScan = require('object-scan');
const get = (obj, p) => objectScan([p], { abort: true, rtn: 'value' })(obj);
const obj = { a: { b: '1', c: '2' } };
console.log(get(obj, 'a.b'));
// => 1
console.log(get(obj, '*.c'));
// => 2
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan
There are a lot more advanced examples in the readme.
This is one of those cases, where you ask 10 developers and you get 10 answers.
Below is my [simplified] solution for OP, using dynamic programming.
The idea is that you would pass an existing DTO object that you wish to UPDATE. This makes the method most useful in the case where you have a form with several input elements having name attributes set with dot (fluent) syntax.
Example use:
<input type="text" name="person.contact.firstName" />
Code snippet:
const setFluently = (obj, path, value) => {
if (typeof path === "string") {
return setFluently(obj, path.split("."), value);
}
if (path.length <= 1) {
obj[path[0]] = value;
return obj;
}
const key = path[0];
obj[key] = setFluently(obj[key] ? obj[key] : {}, path.slice(1), value);
return obj;
};
const origObj = {
a: {
b: "1",
c: "2"
}
};
setFluently(origObj, "a.b", "3");
setFluently(origObj, "a.c", "4");
console.log(JSON.stringify(origObj, null, 3));
function at(obj, path, val = undefined) {
// If path is an Array,
if (Array.isArray(path)) {
// it returns the mapped array for each result of the path
return path.map((path) => at(obj, path, val));
}
// Uniting several RegExps into one
const rx = new RegExp(
[
/(?:^(?:\.\s*)?([_a-zA-Z][_a-zA-Z0-9]*))/,
/(?:^\[\s*(\d+)\s*\])/,
/(?:^\[\s*'([^']*(?:\\'[^']*)*)'\s*\])/,
/(?:^\[\s*"([^"]*(?:\\"[^"]*)*)"\s*\])/,
/(?:^\[\s*`([^`]*(?:\\`[^`]*)*)`\s*\])/,
]
.map((r) => r.source)
.join("|")
);
let rm;
while (rm = rx.exec(path.trim())) {
// Matched resource
let [rf, rp] = rm.filter(Boolean);
// If no one matches found,
if (!rm[1] && !rm[2]) {
// it will replace escape-chars
rp = rp.replace(/\\(.)/g, "$1");
}
// If the new value is set,
if ("undefined" != typeof val && path.length == rf.length) {
// assign a value to the object property and return it
return (obj[rp] = val);
}
// Going one step deeper
obj = obj[rp];
// Removing a step from the path
path = path.substr(rf.length).trim();
}
if (path) {
throw new SyntaxError();
}
return obj;
}
// Test object schema
let o = { a: { b: [ [ { c: { d: { '"e"': { f: { g: "xxx" } } } } } ] ] } };
// Print source object
console.log(JSON.stringify(o));
// Set value
console.log(at(o, '.a["b"][0][0].c[`d`]["\\"e\\""][\'f\']["g"]', "zzz"));
// Get value
console.log(at(o, '.a["b"][0][0].c[`d`]["\\"e\\""][\'f\']["g"]'));
// Print result object
console.log(JSON.stringify(o));
Here is my code without using eval. It’s easy to understand too.
function value(obj, props) {
if (!props)
return obj;
var propsArr = props.split('.');
var prop = propsArr.splice(0, 1);
return value(obj[prop], propsArr.join('.'));
}
var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};
console.log(value(obj, 'a.d.a.b')); // Returns blah
Yes, extending base prototypes is not usually good idea but, if you keep all extensions in one place, they might be useful.
So, here is my way to do this.
Object.defineProperty(Object.prototype, "getNestedProperty", {
value : function (propertyName) {
var result = this;
var arr = propertyName.split(".");
while (arr.length && result) {
result = result[arr.shift()];
}
return result;
},
enumerable: false
});
Now you will be able to get nested property everywhere without importing module with function or copy/pasting function.
Example:
{a:{b:11}}.getNestedProperty('a.b'); // Returns 11
The Next.js extension broke Mongoose in my project. Also I've read that it might break jQuery. So, never do it in the Next.js way:
Object.prototype.getNestedProperty = function (propertyName) {
var result = this;
var arr = propertyName.split(".");
while (arr.length && result) {
result = result[arr.shift()];
}
return result;
};
This is my extended solution proposed by ninjagecko.
For me, simple string notation was not enough, so the below version supports things like:
index(obj, 'data.accounts[0].address[0].postcode');
 
/**
* Get object by index
* #supported
* - arrays supported
* - array indexes supported
* #not-supported
* - multiple arrays
* #issues:
* index(myAccount, 'accounts[0].address[0].id') - works fine
* index(myAccount, 'accounts[].address[0].id') - doesnt work
* #Example:
* index(obj, 'data.accounts[].id') => returns array of id's
* index(obj, 'data.accounts[0].id') => returns id of 0 element from array
* index(obj, 'data.accounts[0].addresses.list[0].id') => error
* #param obj
* #param path
* #returns {any}
*/
var index = function(obj, path, isArray?, arrIndex?){
// is an array
if(typeof isArray === 'undefined') isArray = false;
// array index,
// if null, will take all indexes
if(typeof arrIndex === 'undefined') arrIndex = null;
var _arrIndex = null;
var reduceArrayTag = function(i, subArrIndex){
return i.replace(/(\[)([\d]{0,})(\])/, (i) => {
var tmp = i.match(/(\[)([\d]{0,})(\])/);
isArray = true;
if(subArrIndex){
_arrIndex = (tmp[2] !== '') ? tmp[2] : null;
}else{
arrIndex = (tmp[2] !== '') ? tmp[2] : null;
}
return '';
});
}
function byIndex(obj, i) {
// if is an array
if(isArray){
isArray = false;
i = reduceArrayTag(i, true);
// if array index is null,
// return an array of with values from every index
if(!arrIndex){
var arrValues = [];
_.forEach(obj, (el) => {
arrValues.push(index(el, i, isArray, arrIndex));
})
return arrValues;
}
// if array index is specified
var value = obj[arrIndex][i];
if(isArray){
arrIndex = _arrIndex;
}else{
arrIndex = null;
}
return value;
}else{
// remove [] from notation,
// if [] has been removed, check the index of array
i = reduceArrayTag(i, false);
return obj[i]
}
}
// reduce with the byIndex method
return path.split('.').reduce(byIndex, obj)
}

How to check if an object exists before getting value? [duplicate]

In my code, I deal with an array that has some entries with many objects nested inside one another, where as some do not. It looks something like the following:
// where this array is hundreds of entries long, with a mix
// of the two examples given
var test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
This is giving me problems because I need to iterate through the array at times, and the inconsistency is throwing me errors like so:
for (i=0; i<test.length; i++) {
// ok on i==0, but 'cannot read property of undefined' on i==1
console.log(a.b.c);
}
I am aware that I can say if(a.b){ console.log(a.b.c)}, but this is extraordinarily tedious in cases where there are up to 5 or 6 objects nested within one another. Is there any other (easier) way that I can have it ONLY do the console.log if it exists, but without throwing an error?
Update:
If you use JavaScript according to ECMAScript 2020 or later, see optional chaining.
TypeScript has added support for optional chaining in version 3.7.
// use it like this
obj?.a?.lot?.of?.properties
Solution for JavaScript before ECMASCript 2020 or TypeScript older than version 3.7:
A quick workaround is using a try/catch helper function with ES6 arrow function:
function getSafe(fn, defaultVal) {
try {
return fn();
} catch (e) {
return defaultVal;
}
}
// use it like this
console.log(getSafe(() => obj.a.lot.of.properties));
// or add an optional default value
console.log(getSafe(() => obj.a.lot.of.properties, 'nothing'));
What you are doing raises an exception (and rightfully so).
You can always do:
try{
window.a.b.c
}catch(e){
console.log("YO",e)
}
But I wouldn't, instead think of your use case.
Why are you accessing data, 6 levels nested that you are unfamiliar of? What use case justifies this?
Usually, you'd like to actually validate what sort of object you're dealing with.
Also, on a side note you should not use statements like if(a.b) because it will return false if a.b is 0 or even if it is "0". Instead check if a.b !== undefined
If I am understanding your question correctly, you want the safest way to determine if an object contains a property.
The easiest way is to use the in operator.
window.a = "aString";
//window should have 'a' property
//lets test if it exists
if ("a" in window){
//true
}
if ("b" in window){
//false
}
Of course you can nest this as deep as you want
if ("a" in window.b.c) { }
Not sure if this helps.
Try this. If a.b is undefined, it will leave the if statement without any exception.
if (a.b && a.b.c) {
console.log(a.b.c);
}
If you are using lodash, you could use their has function. It is similar to the native "in", but allows paths.
var testObject = {a: {b: {c: 'walrus'}}};
if(_.has(testObject, 'a.b.c')) {
//Safely access your walrus here
}
If you use Babel, you can already use the optional chaining syntax with #babel/plugin-proposal-optional-chaining Babel plugin. This would allow you to replace this:
console.log(a && a.b && a.b.c);
with this:
console.log(a?.b?.c);
If you have lodash you can use its .get method
_.get(a, 'b.c.d.e')
or give it a default value
_.get(a, 'b.c.d.e', default)
I use undefsafe religiously. It tests each level down into your object until it either gets the value you asked for, or it returns "undefined". But never errors.
This is a common issue when working with deep or complex json object, so I try to avoid try/catch or embedding multiple checks which would make the code unreadable, I usually use this little piece of code in all my procect to do the job.
/* ex: getProperty(myObj,'aze.xyz',0) // return myObj.aze.xyz safely
* accepts array for property names:
* getProperty(myObj,['aze','xyz'],{value: null})
*/
function getProperty(obj, props, defaultValue) {
var res, isvoid = function(x){return typeof x === "undefined" || x === null;}
if(!isvoid(obj)){
if(isvoid(props)) props = [];
if(typeof props === "string") props = props.trim().split(".");
if(props.constructor === Array){
res = props.length>1 ? getProperty(obj[props.shift()],props,defaultValue) : obj[props[0]];
}
}
return typeof res === "undefined" ? defaultValue: res;
}
I like Cao Shouguang's answer, but I am not fond of passing a function as parameter into the getSafe function each time I do the call. I have modified the getSafe function to accept simple parameters and pure ES5.
/**
* Safely get object properties.
* #param {*} prop The property of the object to retrieve
* #param {*} defaultVal The value returned if the property value does not exist
* #returns If property of object exists it is returned,
* else the default value is returned.
* #example
* var myObj = {a : {b : 'c'} };
* var value;
*
* value = getSafe(myObj.a.b,'No Value'); //returns c
* value = getSafe(myObj.a.x,'No Value'); //returns 'No Value'
*
* if (getSafe(myObj.a.x, false)){
* console.log('Found')
* } else {
* console.log('Not Found')
* }; //logs 'Not Found'
*
* if(value = getSafe(myObj.a.b, false)){
* console.log('New Value is', value); //logs 'New Value is c'
* }
*/
function getSafe(prop, defaultVal) {
return function(fn, defaultVal) {
try {
if (fn() === undefined) {
return defaultVal;
} else {
return fn();
}
} catch (e) {
return defaultVal;
}
}(function() {return prop}, defaultVal);
}
Lodash has a get method which allows for a default as an optional third parameter, as show below:
const myObject = {
has: 'some',
missing: {
vars: true
}
}
const path = 'missing.const.value';
const myValue = _.get(myObject, path, 'default');
console.log(myValue) // prints out default, which is specified above
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
Imagine that we want to apply a series of functions to x if and only if x is non-null:
if (x !== null) x = a(x);
if (x !== null) x = b(x);
if (x !== null) x = c(x);
Now let's say that we need to do the same to y:
if (y !== null) y = a(y);
if (y !== null) y = b(y);
if (y !== null) y = c(y);
And the same to z:
if (z !== null) z = a(z);
if (z !== null) z = b(z);
if (z !== null) z = c(z);
As you can see without a proper abstraction, we'll end up duplicating code over and over again. Such an abstraction already exists: the Maybe monad.
The Maybe monad holds both a value and a computational context:
The monad keeps the value safe and applies functions to it.
The computational context is a null check before applying a function.
A naive implementation would look like this:
⚠️ This implementation is for illustration purpose only! This is not how it should be done and is wrong at many levels. However this should give you a better idea of what I am talking about.
As you can see nothing can break:
We apply a series of functions to our value
If at any point, the value becomes null (or undefined) we just don't apply any function anymore.
const abc = obj =>
Maybe
.of(obj)
.map(o => o.a)
.map(o => o.b)
.map(o => o.c)
.value;
const values = [
{},
{a: {}},
{a: {b: {}}},
{a: {b: {c: 42}}}
];
console.log(
values.map(abc)
);
<script>
function Maybe(x) {
this.value = x; //-> container for our value
}
Maybe.of = x => new Maybe(x);
Maybe.prototype.map = function (fn) {
if (this.value == null) { //-> computational context
return this;
}
return Maybe.of(fn(this.value));
};
</script>
Appendix 1
I cannot explain what monads are as this is not the purpose of this post and there are people out there better at this than I am. However as Eric Elliot said in hist blog post JavaScript Monads Made Simple:
Regardless of your skill level or understanding of category theory, using monads makes your code easier to work with. Failing to take advantage of monads may make your code harder to work with (e.g., callback hell, nested conditional branches, more verbosity).
Appendix 2
Here's how I'd solve your issue using the Maybe monad from monetjs
const prop = key => obj => Maybe.fromNull(obj[key]);
const abc = obj =>
Maybe
.fromNull(obj)
.flatMap(prop('a'))
.flatMap(prop('b'))
.flatMap(prop('c'))
.orSome('🌯')
const values = [
{},
{a: {}},
{a: {b: {}}},
{a: {b: {c: 42}}}
];
console.log(
values.map(abc)
);
<script src="https://www.unpkg.com/monet#0.9.0/dist/monet.js"></script>
<script>const {Maybe} = Monet;</script>
In str's answer, value 'undefined' will be returned instead of the set default value if the property is undefined. This sometimes can cause bugs. The following will make sure the defaultVal will always be returned when either the property or the object is undefined.
const temp = {};
console.log(getSafe(()=>temp.prop, '0'));
function getSafe(fn, defaultVal) {
try {
if (fn() === undefined || fn() === null) {
return defaultVal
} else {
return fn();
}
} catch (e) {
return defaultVal;
}
}
You can use optional chaining from the ECMAScript standart.
Like this:
a?.b?.c?.d?.func?.()
I answered this before and happened to be doing a similar check today. A simplification to check if a nested dotted property exists. You could modify this to return the value, or some default to accomplish your goal.
function containsProperty(instance, propertyName) {
// make an array of properties to walk through because propertyName can be nested
// ex "test.test2.test.test"
let walkArr = propertyName.indexOf('.') > 0 ? propertyName.split('.') : [propertyName];
// walk the tree - if any property does not exist then return false
for (let treeDepth = 0, maxDepth = walkArr.length; treeDepth < maxDepth; treeDepth++) {
// property does not exist
if (!Object.prototype.hasOwnProperty.call(instance, walkArr[treeDepth])) {
return false;
}
// does it exist - reassign the leaf
instance = instance[walkArr[treeDepth]];
}
// default
return true;
}
In your question you could do something like:
let test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
containsProperty(test[0], 'a.b.c');
I usually use like this:
var x = object.any ? object.any.a : 'def';
You can avoid getting an error by giving a default value before getting the property
var test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
for (i=0; i<test.length; i++) {
const obj = test[i]
// No error, just undefined, which is ok
console.log(((obj.a || {}).b || {}).c);
}
This works great with arrays too:
const entries = [{id: 1, name: 'Scarllet'}]
// Giving a default name when is empty
const name = (entries.find(v => v.id === 100) || []).name || 'no-name'
console.log(name)
Unrelated to the question's actual question, but might be useful for people coming to this question looking for answers.
Check your function parameters.
If you have a function like const x({ a }) => { }, and you call it without arguments x(); append = {} to the parameter: const x({ a } = {}) => { }.
What I had
I had a function like this:
const x = ({ a }) => console.log(a);
// This one works as expected
x({ a: 1 });
// This one errors out
x();
Which results in "Uncaught TypeError: Cannot destructure property 'a' of 'undefined' as it is undefined."
What I switched it to (now works).
const x = ({ a } = {}) => console.log(a);
// This one works as expected
x({ a: 1 });
// This now works too!
x();

How to pass props from a container component to a class component [duplicate]

In my code, I deal with an array that has some entries with many objects nested inside one another, where as some do not. It looks something like the following:
// where this array is hundreds of entries long, with a mix
// of the two examples given
var test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
This is giving me problems because I need to iterate through the array at times, and the inconsistency is throwing me errors like so:
for (i=0; i<test.length; i++) {
// ok on i==0, but 'cannot read property of undefined' on i==1
console.log(a.b.c);
}
I am aware that I can say if(a.b){ console.log(a.b.c)}, but this is extraordinarily tedious in cases where there are up to 5 or 6 objects nested within one another. Is there any other (easier) way that I can have it ONLY do the console.log if it exists, but without throwing an error?
Update:
If you use JavaScript according to ECMAScript 2020 or later, see optional chaining.
TypeScript has added support for optional chaining in version 3.7.
// use it like this
obj?.a?.lot?.of?.properties
Solution for JavaScript before ECMASCript 2020 or TypeScript older than version 3.7:
A quick workaround is using a try/catch helper function with ES6 arrow function:
function getSafe(fn, defaultVal) {
try {
return fn();
} catch (e) {
return defaultVal;
}
}
// use it like this
console.log(getSafe(() => obj.a.lot.of.properties));
// or add an optional default value
console.log(getSafe(() => obj.a.lot.of.properties, 'nothing'));
What you are doing raises an exception (and rightfully so).
You can always do:
try{
window.a.b.c
}catch(e){
console.log("YO",e)
}
But I wouldn't, instead think of your use case.
Why are you accessing data, 6 levels nested that you are unfamiliar of? What use case justifies this?
Usually, you'd like to actually validate what sort of object you're dealing with.
Also, on a side note you should not use statements like if(a.b) because it will return false if a.b is 0 or even if it is "0". Instead check if a.b !== undefined
If I am understanding your question correctly, you want the safest way to determine if an object contains a property.
The easiest way is to use the in operator.
window.a = "aString";
//window should have 'a' property
//lets test if it exists
if ("a" in window){
//true
}
if ("b" in window){
//false
}
Of course you can nest this as deep as you want
if ("a" in window.b.c) { }
Not sure if this helps.
Try this. If a.b is undefined, it will leave the if statement without any exception.
if (a.b && a.b.c) {
console.log(a.b.c);
}
If you are using lodash, you could use their has function. It is similar to the native "in", but allows paths.
var testObject = {a: {b: {c: 'walrus'}}};
if(_.has(testObject, 'a.b.c')) {
//Safely access your walrus here
}
If you use Babel, you can already use the optional chaining syntax with #babel/plugin-proposal-optional-chaining Babel plugin. This would allow you to replace this:
console.log(a && a.b && a.b.c);
with this:
console.log(a?.b?.c);
If you have lodash you can use its .get method
_.get(a, 'b.c.d.e')
or give it a default value
_.get(a, 'b.c.d.e', default)
I use undefsafe religiously. It tests each level down into your object until it either gets the value you asked for, or it returns "undefined". But never errors.
This is a common issue when working with deep or complex json object, so I try to avoid try/catch or embedding multiple checks which would make the code unreadable, I usually use this little piece of code in all my procect to do the job.
/* ex: getProperty(myObj,'aze.xyz',0) // return myObj.aze.xyz safely
* accepts array for property names:
* getProperty(myObj,['aze','xyz'],{value: null})
*/
function getProperty(obj, props, defaultValue) {
var res, isvoid = function(x){return typeof x === "undefined" || x === null;}
if(!isvoid(obj)){
if(isvoid(props)) props = [];
if(typeof props === "string") props = props.trim().split(".");
if(props.constructor === Array){
res = props.length>1 ? getProperty(obj[props.shift()],props,defaultValue) : obj[props[0]];
}
}
return typeof res === "undefined" ? defaultValue: res;
}
I like Cao Shouguang's answer, but I am not fond of passing a function as parameter into the getSafe function each time I do the call. I have modified the getSafe function to accept simple parameters and pure ES5.
/**
* Safely get object properties.
* #param {*} prop The property of the object to retrieve
* #param {*} defaultVal The value returned if the property value does not exist
* #returns If property of object exists it is returned,
* else the default value is returned.
* #example
* var myObj = {a : {b : 'c'} };
* var value;
*
* value = getSafe(myObj.a.b,'No Value'); //returns c
* value = getSafe(myObj.a.x,'No Value'); //returns 'No Value'
*
* if (getSafe(myObj.a.x, false)){
* console.log('Found')
* } else {
* console.log('Not Found')
* }; //logs 'Not Found'
*
* if(value = getSafe(myObj.a.b, false)){
* console.log('New Value is', value); //logs 'New Value is c'
* }
*/
function getSafe(prop, defaultVal) {
return function(fn, defaultVal) {
try {
if (fn() === undefined) {
return defaultVal;
} else {
return fn();
}
} catch (e) {
return defaultVal;
}
}(function() {return prop}, defaultVal);
}
Lodash has a get method which allows for a default as an optional third parameter, as show below:
const myObject = {
has: 'some',
missing: {
vars: true
}
}
const path = 'missing.const.value';
const myValue = _.get(myObject, path, 'default');
console.log(myValue) // prints out default, which is specified above
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
Imagine that we want to apply a series of functions to x if and only if x is non-null:
if (x !== null) x = a(x);
if (x !== null) x = b(x);
if (x !== null) x = c(x);
Now let's say that we need to do the same to y:
if (y !== null) y = a(y);
if (y !== null) y = b(y);
if (y !== null) y = c(y);
And the same to z:
if (z !== null) z = a(z);
if (z !== null) z = b(z);
if (z !== null) z = c(z);
As you can see without a proper abstraction, we'll end up duplicating code over and over again. Such an abstraction already exists: the Maybe monad.
The Maybe monad holds both a value and a computational context:
The monad keeps the value safe and applies functions to it.
The computational context is a null check before applying a function.
A naive implementation would look like this:
⚠️ This implementation is for illustration purpose only! This is not how it should be done and is wrong at many levels. However this should give you a better idea of what I am talking about.
As you can see nothing can break:
We apply a series of functions to our value
If at any point, the value becomes null (or undefined) we just don't apply any function anymore.
const abc = obj =>
Maybe
.of(obj)
.map(o => o.a)
.map(o => o.b)
.map(o => o.c)
.value;
const values = [
{},
{a: {}},
{a: {b: {}}},
{a: {b: {c: 42}}}
];
console.log(
values.map(abc)
);
<script>
function Maybe(x) {
this.value = x; //-> container for our value
}
Maybe.of = x => new Maybe(x);
Maybe.prototype.map = function (fn) {
if (this.value == null) { //-> computational context
return this;
}
return Maybe.of(fn(this.value));
};
</script>
Appendix 1
I cannot explain what monads are as this is not the purpose of this post and there are people out there better at this than I am. However as Eric Elliot said in hist blog post JavaScript Monads Made Simple:
Regardless of your skill level or understanding of category theory, using monads makes your code easier to work with. Failing to take advantage of monads may make your code harder to work with (e.g., callback hell, nested conditional branches, more verbosity).
Appendix 2
Here's how I'd solve your issue using the Maybe monad from monetjs
const prop = key => obj => Maybe.fromNull(obj[key]);
const abc = obj =>
Maybe
.fromNull(obj)
.flatMap(prop('a'))
.flatMap(prop('b'))
.flatMap(prop('c'))
.orSome('🌯')
const values = [
{},
{a: {}},
{a: {b: {}}},
{a: {b: {c: 42}}}
];
console.log(
values.map(abc)
);
<script src="https://www.unpkg.com/monet#0.9.0/dist/monet.js"></script>
<script>const {Maybe} = Monet;</script>
In str's answer, value 'undefined' will be returned instead of the set default value if the property is undefined. This sometimes can cause bugs. The following will make sure the defaultVal will always be returned when either the property or the object is undefined.
const temp = {};
console.log(getSafe(()=>temp.prop, '0'));
function getSafe(fn, defaultVal) {
try {
if (fn() === undefined || fn() === null) {
return defaultVal
} else {
return fn();
}
} catch (e) {
return defaultVal;
}
}
You can use optional chaining from the ECMAScript standart.
Like this:
a?.b?.c?.d?.func?.()
I answered this before and happened to be doing a similar check today. A simplification to check if a nested dotted property exists. You could modify this to return the value, or some default to accomplish your goal.
function containsProperty(instance, propertyName) {
// make an array of properties to walk through because propertyName can be nested
// ex "test.test2.test.test"
let walkArr = propertyName.indexOf('.') > 0 ? propertyName.split('.') : [propertyName];
// walk the tree - if any property does not exist then return false
for (let treeDepth = 0, maxDepth = walkArr.length; treeDepth < maxDepth; treeDepth++) {
// property does not exist
if (!Object.prototype.hasOwnProperty.call(instance, walkArr[treeDepth])) {
return false;
}
// does it exist - reassign the leaf
instance = instance[walkArr[treeDepth]];
}
// default
return true;
}
In your question you could do something like:
let test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
containsProperty(test[0], 'a.b.c');
I usually use like this:
var x = object.any ? object.any.a : 'def';
You can avoid getting an error by giving a default value before getting the property
var test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
for (i=0; i<test.length; i++) {
const obj = test[i]
// No error, just undefined, which is ok
console.log(((obj.a || {}).b || {}).c);
}
This works great with arrays too:
const entries = [{id: 1, name: 'Scarllet'}]
// Giving a default name when is empty
const name = (entries.find(v => v.id === 100) || []).name || 'no-name'
console.log(name)
Unrelated to the question's actual question, but might be useful for people coming to this question looking for answers.
Check your function parameters.
If you have a function like const x({ a }) => { }, and you call it without arguments x(); append = {} to the parameter: const x({ a } = {}) => { }.
What I had
I had a function like this:
const x = ({ a }) => console.log(a);
// This one works as expected
x({ a: 1 });
// This one errors out
x();
Which results in "Uncaught TypeError: Cannot destructure property 'a' of 'undefined' as it is undefined."
What I switched it to (now works).
const x = ({ a } = {}) => console.log(a);
// This one works as expected
x({ a: 1 });
// This now works too!
x();

ES6 / lodash - check nested Object values with startsWith

is there any function or any fast way to check if some value in our object startsWith e.g asd
Example:
let obj = {
'child' : {
'child_key': 'asdfghhj'
},
'free': 'notasd',
'with': 'asdhaheg'
}
// check here if our obj has value that startsWith('asd')
Regards
Use #trincot's solution if you really don't care about which node/value matched. It's straightforward, well-written, and solves your problem very effectively.
If you want more than just a Boolean value as the result of your digging, read along ...
I really doubt your need for this, but if your object is significantly large, you will want an early exit behaviour – what this means is that as soon as a match is found, iteration through your input data will stop and true/false result will be returned immediately. #trincot's solution offers early exit, but solutions that using map, filter, or reduce offer no such behaviour.
findDeep is much more useful than just checking if a string value starts with another string value – it takes a higher-order function that is applied for each leaf node in your data.
This answer uses my findDeep procedure to define a generic anyStartsWith procedure by checking if findDeep returns undefined (no match)
It will work any any input type and it will traverse Object and Array child nodes.
const isObject = x=> Object(x) === x
const isArray = Array.isArray
const keys = Object.keys
const rest = ([x,...xs]) => xs
const findDeep = f => x => {
let make = (x,ks)=> ({node: x, keys: ks || keys(x)})
let processNode = (parents, path, {node, keys:[k,...ks]})=> {
if (k === undefined)
return loop(parents, rest(path))
else if (isArray(node[k]) || isObject(node[k]))
return loop([make(node[k]), make(node, ks), ...parents], [k, ...path])
else if (f(node[k], k))
return {parents, path: [k,...path], node}
else
return loop([{node, keys: ks}, ...parents], path)
}
let loop = ([node,...parents], path) => {
if (node === undefined)
return undefined
else
return processNode(parents, path, node)
}
return loop([make(x)], [])
}
const startsWith = x => y => y.indexOf(x) === 0
const anyStartsWith = x => xs => findDeep (startsWith(x)) (xs) !== undefined
let obj = {
'child' : {
'child_key': 'asdfghhj'
},
'free': 'notasd',
'with': 'asdhaheg'
}
console.log(anyStartsWith ('asd') (obj)) // true
console.log(anyStartsWith ('candy') (obj)) // false
You'll see this is kind of a waste of findDeep's potential, but if you don't need it's power then it's not for you.
Here's the real power of findDeep
findDeep (startsWith('asd')) (obj)
// =>
{
parents: [
{
node: {
child: {
child_key: 'asdfghhj'
},
free: 'notasd',
with: 'asdhaheg'
},
keys: [ 'free', 'with' ]
}
],
path: [ 'child_key', 'child' ],
node: {
child_key: 'asdfghhj'
}
}
The resulting object has 3 properties
parents – the full object reference to each node in the matched value's lineage
path – the path of keys to get to the matched value (stack reversed)
node – the key/value pair that matched
You can see that if we take the parent object as p and reverse the path stack, we get to the matched value
p['child']['child_key']; //=> 'asdfghhj'
Here is a function with mild ES6 usage:
function startsWithRecursive(obj, needle) {
return obj != null &&
(typeof obj === "object"
? Object.keys(obj).some( key => startsWithRecursive(obj[key], needle) )
: String(obj).startsWith(needle));
}
// Sample data
let obj = {
'child' : {
'child_key': 'asdfghhj'
},
'free': 'notasd',
'with': 'asdhaheg'
};
// Requests
console.log( 'obj, "asd":', startsWithRecursive(obj, 'asd' ) );
console.log( 'obj, "hello":', startsWithRecursive(obj, 'hello' ) );
console.log( 'null, "":', startsWithRecursive(null, '' ) );
console.log( 'undefined, "":', startsWithRecursive(undefined, '' ) );
console.log( '"test", "te":', startsWithRecursive('test', 'te' ) );
console.log( '12.5, 1:', startsWithRecursive(12.5, 1 ) );
Explanation:
The function is recursive: it calls itself as it goes through a nested object structure. The value passed as obj can fall in one of the following three categories:
It is equivalent to null (like also undefined): in that case neither a recursive call, nor a call of the startsWith method can be made: the result is false as this value obviously does not start with the given search string;
It is an object: in that case that object's property values should be inspected. This will be done through recursive calls. The some method makes sure that as soon a match has been found, the iteration stops, and no further property values are inspected. In that case some returns true. If none of the property values matched, some returns false;
It is none of the above. In that case we cast it to string (by applying the String function) and apply startsWith on it.
The value calculated in the applicable step will be returned as function result. If this was a recursive call, it will be treated as return value in the some callback, ...etc.
Note that this function also returns the correct result when you call it on a string, like so:
startsWithRecursive('test', 'te'); // true
Non-Recursive Alternative
In answer to comments about potential stack limitations, here is an alternative non-recursive function which maintains a "stack" in a variable:
function startsWithRecursive(obj, needle) {
var stack = [obj];
while (stack.length) {
obj = stack.pop();
if (obj != null) {
if (typeof obj === "object") {
stack = stack.concat(Object.keys(obj).map( key => obj[key] ));
} else {
if (String(obj).startsWith(needle)) return true;
}
}
}
return false;
}
You can recursively iterate object properties and check if property starts with prefix using find function:
function hasPropertyStartingWith(obj, prefix) {
return !!Object.keys(obj).find(key => {
if (typeof obj[key] === 'object') {
return hasPropertyStartingWith(obj[key], prefix)
}
if (typeof obj[key] === 'string') {
return obj[key].startsWith(prefix)
}
return false
})
}
console.log(hasPropertyStartingWith(obj, 'asd'))
You may get away with something as simple as using a RegExp on a JSON string, something like
var obj = {
'child': {
'child_key': 'asdfghhj'
},
'free': 'notasd',
'with': 'asdhaheg'
};
function customStartsWith(obj, prefix) {
return new RegExp(':"' + prefix + '[\\s\\S]*?"').test(JSON.stringify(obj));
}
console.log('obj, "asd":', customStartsWith(obj, 'asd'));
console.log('obj, "hello":', customStartsWith(obj, 'hello'));
console.log('null, "":', customStartsWith(null, ''));
console.log('undefined, "":', customStartsWith(undefined, ''));
console.log('"test", "te":', customStartsWith('test', 'te'));
console.log('12.5, 1:', customStartsWith(12.5, 1));
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.9/es5-shim.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/json3/3.3.2/json3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.35.1/es6-shim.js"></script>
Update: Another recursive object walker that will work in a shimmed environment. This is just an example and it is easily customised.
var walk = returnExports;
var obj = {
'child': {
'child_key': 'asdfghhj'
},
'free': 'notasd',
'with': 'asdhaheg'
};
function customStartsWith(obj, prefix) {
var found = false;
walk(obj, Object.keys, function(value) {
if (typeof value === 'string' && value.startsWith(prefix)) {
found = true;
walk.BREAK;
}
});
return found;
}
console.log('obj, "asd":', customStartsWith(obj, 'asd'));
console.log('obj, "hello":', customStartsWith(obj, 'hello'));
console.log('null, "":', customStartsWith(null, ''));
console.log('undefined, "":', customStartsWith(undefined, ''));
console.log('"test", "te":', customStartsWith('test', 'te'));
console.log('12.5, 1:', customStartsWith(12.5, 1));
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.9/es5-shim.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/json3/3.3.2/json3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.35.1/es6-shim.js"></script>
<script src="https://rawgithub.com/Xotic750/object-walk-x/master/lib/object-walk-x.js"></script>

How to avoid 'cannot read property of undefined' errors?

In my code, I deal with an array that has some entries with many objects nested inside one another, where as some do not. It looks something like the following:
// where this array is hundreds of entries long, with a mix
// of the two examples given
var test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
This is giving me problems because I need to iterate through the array at times, and the inconsistency is throwing me errors like so:
for (i=0; i<test.length; i++) {
// ok on i==0, but 'cannot read property of undefined' on i==1
console.log(a.b.c);
}
I am aware that I can say if(a.b){ console.log(a.b.c)}, but this is extraordinarily tedious in cases where there are up to 5 or 6 objects nested within one another. Is there any other (easier) way that I can have it ONLY do the console.log if it exists, but without throwing an error?
Update:
If you use JavaScript according to ECMAScript 2020 or later, see optional chaining.
TypeScript has added support for optional chaining in version 3.7.
// use it like this
obj?.a?.lot?.of?.properties
Solution for JavaScript before ECMASCript 2020 or TypeScript older than version 3.7:
A quick workaround is using a try/catch helper function with ES6 arrow function:
function getSafe(fn, defaultVal) {
try {
return fn();
} catch (e) {
return defaultVal;
}
}
// use it like this
console.log(getSafe(() => obj.a.lot.of.properties));
// or add an optional default value
console.log(getSafe(() => obj.a.lot.of.properties, 'nothing'));
What you are doing raises an exception (and rightfully so).
You can always do:
try{
window.a.b.c
}catch(e){
console.log("YO",e)
}
But I wouldn't, instead think of your use case.
Why are you accessing data, 6 levels nested that you are unfamiliar of? What use case justifies this?
Usually, you'd like to actually validate what sort of object you're dealing with.
Also, on a side note you should not use statements like if(a.b) because it will return false if a.b is 0 or even if it is "0". Instead check if a.b !== undefined
If I am understanding your question correctly, you want the safest way to determine if an object contains a property.
The easiest way is to use the in operator.
window.a = "aString";
//window should have 'a' property
//lets test if it exists
if ("a" in window){
//true
}
if ("b" in window){
//false
}
Of course you can nest this as deep as you want
if ("a" in window.b.c) { }
Not sure if this helps.
Try this. If a.b is undefined, it will leave the if statement without any exception.
if (a.b && a.b.c) {
console.log(a.b.c);
}
If you are using lodash, you could use their has function. It is similar to the native "in", but allows paths.
var testObject = {a: {b: {c: 'walrus'}}};
if(_.has(testObject, 'a.b.c')) {
//Safely access your walrus here
}
If you use Babel, you can already use the optional chaining syntax with #babel/plugin-proposal-optional-chaining Babel plugin. This would allow you to replace this:
console.log(a && a.b && a.b.c);
with this:
console.log(a?.b?.c);
If you have lodash you can use its .get method
_.get(a, 'b.c.d.e')
or give it a default value
_.get(a, 'b.c.d.e', default)
I use undefsafe religiously. It tests each level down into your object until it either gets the value you asked for, or it returns "undefined". But never errors.
This is a common issue when working with deep or complex json object, so I try to avoid try/catch or embedding multiple checks which would make the code unreadable, I usually use this little piece of code in all my procect to do the job.
/* ex: getProperty(myObj,'aze.xyz',0) // return myObj.aze.xyz safely
* accepts array for property names:
* getProperty(myObj,['aze','xyz'],{value: null})
*/
function getProperty(obj, props, defaultValue) {
var res, isvoid = function(x){return typeof x === "undefined" || x === null;}
if(!isvoid(obj)){
if(isvoid(props)) props = [];
if(typeof props === "string") props = props.trim().split(".");
if(props.constructor === Array){
res = props.length>1 ? getProperty(obj[props.shift()],props,defaultValue) : obj[props[0]];
}
}
return typeof res === "undefined" ? defaultValue: res;
}
I like Cao Shouguang's answer, but I am not fond of passing a function as parameter into the getSafe function each time I do the call. I have modified the getSafe function to accept simple parameters and pure ES5.
/**
* Safely get object properties.
* #param {*} prop The property of the object to retrieve
* #param {*} defaultVal The value returned if the property value does not exist
* #returns If property of object exists it is returned,
* else the default value is returned.
* #example
* var myObj = {a : {b : 'c'} };
* var value;
*
* value = getSafe(myObj.a.b,'No Value'); //returns c
* value = getSafe(myObj.a.x,'No Value'); //returns 'No Value'
*
* if (getSafe(myObj.a.x, false)){
* console.log('Found')
* } else {
* console.log('Not Found')
* }; //logs 'Not Found'
*
* if(value = getSafe(myObj.a.b, false)){
* console.log('New Value is', value); //logs 'New Value is c'
* }
*/
function getSafe(prop, defaultVal) {
return function(fn, defaultVal) {
try {
if (fn() === undefined) {
return defaultVal;
} else {
return fn();
}
} catch (e) {
return defaultVal;
}
}(function() {return prop}, defaultVal);
}
Lodash has a get method which allows for a default as an optional third parameter, as show below:
const myObject = {
has: 'some',
missing: {
vars: true
}
}
const path = 'missing.const.value';
const myValue = _.get(myObject, path, 'default');
console.log(myValue) // prints out default, which is specified above
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
Imagine that we want to apply a series of functions to x if and only if x is non-null:
if (x !== null) x = a(x);
if (x !== null) x = b(x);
if (x !== null) x = c(x);
Now let's say that we need to do the same to y:
if (y !== null) y = a(y);
if (y !== null) y = b(y);
if (y !== null) y = c(y);
And the same to z:
if (z !== null) z = a(z);
if (z !== null) z = b(z);
if (z !== null) z = c(z);
As you can see without a proper abstraction, we'll end up duplicating code over and over again. Such an abstraction already exists: the Maybe monad.
The Maybe monad holds both a value and a computational context:
The monad keeps the value safe and applies functions to it.
The computational context is a null check before applying a function.
A naive implementation would look like this:
⚠️ This implementation is for illustration purpose only! This is not how it should be done and is wrong at many levels. However this should give you a better idea of what I am talking about.
As you can see nothing can break:
We apply a series of functions to our value
If at any point, the value becomes null (or undefined) we just don't apply any function anymore.
const abc = obj =>
Maybe
.of(obj)
.map(o => o.a)
.map(o => o.b)
.map(o => o.c)
.value;
const values = [
{},
{a: {}},
{a: {b: {}}},
{a: {b: {c: 42}}}
];
console.log(
values.map(abc)
);
<script>
function Maybe(x) {
this.value = x; //-> container for our value
}
Maybe.of = x => new Maybe(x);
Maybe.prototype.map = function (fn) {
if (this.value == null) { //-> computational context
return this;
}
return Maybe.of(fn(this.value));
};
</script>
Appendix 1
I cannot explain what monads are as this is not the purpose of this post and there are people out there better at this than I am. However as Eric Elliot said in hist blog post JavaScript Monads Made Simple:
Regardless of your skill level or understanding of category theory, using monads makes your code easier to work with. Failing to take advantage of monads may make your code harder to work with (e.g., callback hell, nested conditional branches, more verbosity).
Appendix 2
Here's how I'd solve your issue using the Maybe monad from monetjs
const prop = key => obj => Maybe.fromNull(obj[key]);
const abc = obj =>
Maybe
.fromNull(obj)
.flatMap(prop('a'))
.flatMap(prop('b'))
.flatMap(prop('c'))
.orSome('🌯')
const values = [
{},
{a: {}},
{a: {b: {}}},
{a: {b: {c: 42}}}
];
console.log(
values.map(abc)
);
<script src="https://www.unpkg.com/monet#0.9.0/dist/monet.js"></script>
<script>const {Maybe} = Monet;</script>
In str's answer, value 'undefined' will be returned instead of the set default value if the property is undefined. This sometimes can cause bugs. The following will make sure the defaultVal will always be returned when either the property or the object is undefined.
const temp = {};
console.log(getSafe(()=>temp.prop, '0'));
function getSafe(fn, defaultVal) {
try {
if (fn() === undefined || fn() === null) {
return defaultVal
} else {
return fn();
}
} catch (e) {
return defaultVal;
}
}
You can use optional chaining from the ECMAScript standart.
Like this:
a?.b?.c?.d?.func?.()
I answered this before and happened to be doing a similar check today. A simplification to check if a nested dotted property exists. You could modify this to return the value, or some default to accomplish your goal.
function containsProperty(instance, propertyName) {
// make an array of properties to walk through because propertyName can be nested
// ex "test.test2.test.test"
let walkArr = propertyName.indexOf('.') > 0 ? propertyName.split('.') : [propertyName];
// walk the tree - if any property does not exist then return false
for (let treeDepth = 0, maxDepth = walkArr.length; treeDepth < maxDepth; treeDepth++) {
// property does not exist
if (!Object.prototype.hasOwnProperty.call(instance, walkArr[treeDepth])) {
return false;
}
// does it exist - reassign the leaf
instance = instance[walkArr[treeDepth]];
}
// default
return true;
}
In your question you could do something like:
let test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
containsProperty(test[0], 'a.b.c');
I usually use like this:
var x = object.any ? object.any.a : 'def';
You can avoid getting an error by giving a default value before getting the property
var test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
for (i=0; i<test.length; i++) {
const obj = test[i]
// No error, just undefined, which is ok
console.log(((obj.a || {}).b || {}).c);
}
This works great with arrays too:
const entries = [{id: 1, name: 'Scarllet'}]
// Giving a default name when is empty
const name = (entries.find(v => v.id === 100) || []).name || 'no-name'
console.log(name)
Unrelated to the question's actual question, but might be useful for people coming to this question looking for answers.
Check your function parameters.
If you have a function like const x({ a }) => { }, and you call it without arguments x(); append = {} to the parameter: const x({ a } = {}) => { }.
What I had
I had a function like this:
const x = ({ a }) => console.log(a);
// This one works as expected
x({ a: 1 });
// This one errors out
x();
Which results in "Uncaught TypeError: Cannot destructure property 'a' of 'undefined' as it is undefined."
What I switched it to (now works).
const x = ({ a } = {}) => console.log(a);
// This one works as expected
x({ a: 1 });
// This now works too!
x();

Categories