Best way to get deep property in ES6 shorthand notation [duplicate] - javascript

This question already has answers here:
How to avoid 'cannot read property of undefined' errors?
(18 answers)
Closed 5 years ago.
I am running into an issue where in I have a object as below
a = {
b: {
c: 10
}
};
Now this is a dynamic object and can be empty on runtime, like this a = {}, I am trying to read c in ES6 shorthand notation like const {b: {c}} = a;. But getting error every time object is empty. Is there a way I can still use this notation for empty object, like get undefined for c in that case.
I know I can do something like (a.b ? a.b.c : undefined), but i just wanted to know shothand way of doing it.

You can do = {} for inner objects:
const a = {
b: {
c: 10
}
};
const f = ({b: {c} = {}}) => console.log(c)
f(a)
f({})
In this case c becomes undefined if the object is empty

you can do
let a = {
b: {
c: 10
}
};
let result = a && a.b && a.b.c;
console.log(result);
it would return the property if it is present, otherwise it would return undefined

You might want a general approach, which isn't really terse for shallow structures but saves you compiling out a static sequence of conditionals.
function dive (obj, pz) {
return pz.reduce((acc, p) => {
return obj && p in acc ? acc[p] : undefined;
}, obj);
}
You can use this with arbirary depth
console.log(dive({a:{b:{c:[10,11,12]}}}, ['a', 'b', 'c', 1]))
c.f. this demo

Give lodash a shot:
lodash.get(a, 'b.c', FALLBACK_DEFAULT_VALUE);

Related

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();

Can Javascript spread operator include undefined fields of an object?

Is there a more readable way of spreading undefined fields of an object on another object without traversing every element of it?
Following example spreads object A on object B:
let A = { f1:'Foo', f2:'Bar', f3:'Baz' }
let B = { ...A }
// Now B has the value of { f1:'Foo', f2:'Bar', f3:'Baz' }
However in the following example spread operator will not include undefined values:
let A = { f1:'Foo', f2:undefined, f3:'Baz' }
let B = { ...A }
// Now B has the value of { f1:'Foo', f3:'Baz' }
// I would like it to be spread like { f1:'Foo', f2:undefined, f3:'Baz' }
// or { f1:'Foo', f2:null, f3:'Baz' }
Is there a way of projecting fields with undefined value using spread operator? (and obviously WITHOUT traversing every field of the object A and spreading into B if the value of that field is not undefined)
If you're asking if the spread operator will maintain undefined property values 'post spread', they do.
const original = { one: 1, two: 2, three: undefined, four: null };
console.log(original);
const withSpread = {five: 5, ...original };
console.log(withSpread);
console.log(typeof withSpread.three === 'undefined')
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
It turned out to be an invalid assertion of mine. Spread operator indeed spreads fields with undefined value. It was JSON.stringify() removing those fields within one of my sources, which lead me to an invalid assertion.
For Express.js users; you can use app.set('json replacer', (k, v) => v===undefined ? null : v); to let express stringify your json response by replacing undefined values with null
Or likewise, you can use JSON.stringify({...}, (k, v) => v===undefined ? null : v) to let it stringify by replacing undefined values with null

How to deeply copy objects without replacing the entire property in ES6/Javascript [duplicate]

This question already has answers here:
How to deep merge instead of shallow merge?
(47 answers)
Closed 4 years ago.
I want to deeply copy all missing fields into the object shown by the example code below. Is there a quick es6 shortcut for deeply copying the missing properties in the object?
I tried using Object.assign but the problem is that it replaces someKey with the second someKey object, where as I want it to simply copy over all the properties.
Also these objects are just some random demonstrations, let's say the magic code should be property agnostic
const x = {};
const a = { someKey: { first: 1 } };
const b = { someKey: { second: 2 } };
const c = { otherKey: { first: 1 } };
// some magic algorithm to get expected
Object.assign(x, a, b, c); // this doesn't work
const expected = {
someKey: {
first: 1,
second: 2
},
otherKey: {
first: 1
}
};
Voit lá. Run snippet for demonstration.
const merge = function(){
let target = arguments[0];
Array.prototype.shift.apply(arguments);
while (arguments[0]) {
for (let key of Object.keys(arguments[0]))
if (arguments[0][key] instanceof Object)
Object.assign(arguments[0][key], merge(target[key], arguments[0][key]));
Object.assign(target || {}, arguments[0]);
Array.prototype.shift.apply(arguments);
}
return target;
}
const x = {};
const a = { someKey: { first: 1 } };
const b = { someKey: { second: 2 } };
const c = { otherKey: { first: 1 } };
console.log(merge(x,a,b,c));
If you want to do a merge on some keys, you'll have to do it by hand Object.assign works on last-in wins so to merge keys you'll want something like the following...
Object.assign({}, a, b, c, {someKey: Object.assign({}, a.someKey, b.someKey)})
the following code can be read as....
merge "A" "B" and "C" together.... Override someKey with a merge of "A.someKey and b.someKey" together.

Conditionally adding properties to JavaScript object

Is there a more concise or readable way of doing this?
var foo={a:111,c:333, somePropertyThatShouldntBeAdded:'xxx'};
var myobj={x:1,y:2,z:3};
if(foo.a){myobj.a=foo.a;}
if(foo.b){myobj.b=foo.b;}
if(foo.c){myobj.c=foo.c;}
EDIT. Context why I am doing this is below.
var obj={value:this.text,css:{color:'#929292',margin:'1px 0px'}};
if(this.width){obj.css.width=this.width;}
if(this.type){obj.type=this.type;}
if(this.id){obj.id=this.id;}
var input=$('<input/>',obj)
You could use a simple loop-based approach:
var foo={a:111,c:333, somePropertyThatShouldntBeAdded:'xxx'};
var myobj={x:1,y:2,z:3};
['a','b','c'].forEach(function(key) {
if(key in foo) {
myobj[key] = foo[key];
}
});
Notice how I used the in keyword. Your current solution will not work if the value of a property is (e.g.) false or 0.
Additionaly, to get better solutions, provide some context: why are you conditionally copying properties? Perhaps you can avoid this to begin with.
With the introduction of the spread operator in ES2018 you can do something like this.
const original = {
first: null,
second: 'truthy'
};
const conditionalObject = {
...( original.first && { first: original.first }),
...( original.second && { second: original.second })
};
In the conditionalObject we first check if the property we want to add from the original is truthy, if it is we spread that property with its value into the new object.
If the property from the original object is falsy, the && short circuits and the property is never spread into the new object.
You can read a more detailed explanation here
The new conditionalObject will look like this
{
second: 'truthy'
}
You can use the ternary operator like this:
myobj.a = foo.a ? foo.a : undefined;
Though its not exactly the same as the if statement you have, because you'll have {a: undefined} instead of {}. The difference would show up if you ever enumerated the keys of your object.
Edit:
#hindmost has a good suggestion in the comments. You could improve it further with underscore.js:
_.extend(myobj, _.pick(foo, ['a', 'b', 'c']));
You could use jQuery's extend, then delete the properties you don't want:
function extendExcept(sourceObject, targetObject, except) {
var target = $.extend(sourceObject, targetObject);
except.forEach(function(key) {
delete target[key];
});
return target;
}
You could call it like:
var foo={a:111,c:333, somePropertyThatShouldntBeAdded:'xxx'};
var myobj={x:1,y:2,z:3};
myobj = extendExcept(foo, myobj, ["somePropertyThatShouldntBeAdded"]);
Working Example
var foo = {
a: 111,
c: 333,
somePropertyThatShouldntBeAdded: 'xxx'
};
var myObj = {
x: 1,
y: 2,
z: 3
};
for(var key in foo) {
if(key === 'somePropertyThatShouldntBeAdded') {
continue;
}
myObj[key] = foo[key];
}

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