Lodash get vs. es6 fallback values? - javascript

How does Lodash compare to using the new ES6 optional arguments?
I have the following code:
location: {
latitude: response.pickupLocation.latitude || "",
longitude: response.pickupLocation.longitude || ""
},
With Lodash I know I could run:
latitude: get(response, 'pickupLocation.latitude', '')
Or alternatively I could create a function that takes in the object and path and always returns ''as the default fallback. Is there any advantage to using Lodash here other than the fact that the code would be shorter?

The advantage of _.get is, you omit continuing checks if a property exist, which would be necessary.
latitude: response && response.pickupLocation && response.pickupLocation.latitude || "",

For the latest browsers, or by using polyfills or transpiler, you could also use optional chaining and nullish coalescing operator like:
latitude: response?.pickupLocation?.latitude ?? ''

This has nothing to do with ES6, it is just the logical OR operator. "Optional arguments" sounds like you are relating to default parameters but these are used in function signatures.
Personally, I would stick with the logical OR as it does not require an external library. Also I would recommend to never use strings when you want to access object properties because it complicates refactoring, usage search, the optional use of TypeScript, and hinders code completion.
If you want to make sure, that no exception occurs when you access a property of a null type, you can use the approach described here:
getSafe(() => response.pickupLocation.latitude) || ''

Referring to #str's answer, if response does not have a pickupLocation property, this will result in a TypeError (Cannot read property latitude of undefined), regardless of the OR operator.
You could wrap this in a try-catch. Then you would return response.pickupLocation.latitude || fallback in try or fallback in a catch block.
This will not handle the defined by falsey values (imagine that response.pickupLocation.latitude is 0). This function will return a fallback value, not 0. This can be handled easily by checking typeof (typeof response.pickupLocation.latitude !== 'undefined' ? response.pickupLocation.latitude : fallback), however we are still talking about a hardcoded object path.
The same issue is presented on the accepted answer as well.
To handle this dynamically for any object path, you have to loop over a provided object, check for hasOwnProperty and peel off the nested properties by each iteration until you get to end of the specified path. Something like this:
const getSafe = (object, path, fallback) => {
const fragments = path.split('.')
let value
for (let i = 0; i < fragments.length; i++) {
if (!obj.hasOwnProperty(fragments[i])) {
return fallback;
}
value = object[fragments[i]];
}
return value;
}

Related

How to check for empty array result in single line filter method, I want to add validation [duplicate]

I'm trying to use optional chaining with an array instead of an object but not sure how to do that:
Here's what I'm trying to do myArray.filter(x => x.testKey === myTestKey)?[0].
Also trying similar thing with a function:
let x = {a: () => {}, b: null}
console.log(x?b());
But it's giving a similar error - how can I use optional chaining with an array or a function?
You need to put a . after the ? to use optional chaining:
myArray.filter(x => x.testKey === myTestKey)?.[0]
Playground link
Using just the ? alone makes the compiler think you're trying to use the conditional operator (and then it throws an error since it doesn't see a : later)
Optional chaining isn't just a TypeScript thing - it is a finished proposal in plain JavaScript too.
It can be used with bracket notation like above, but it can also be used with dot notation property access:
const obj = {
prop2: {
nested2: 'val2'
}
};
console.log(
obj.prop1?.nested1,
obj.prop2?.nested2
);
And with function calls:
const obj = {
fn2: () => console.log('fn2 running')
};
obj.fn1?.();
obj.fn2?.();
Just found it after a little searching on the what's new page on official documentation
The right way to do it with array is to add . after ?
so it'll be like
myArray.filter(x => x.testKey === myTestKey)?.[0]
I'll like to throw some more light on what exactly happens with my above question case.
myArray.filter(x => x.testKey === myTestKey)?[0]
Transpiles to
const result = myArray.filter(x => x.testKey === myTestKey) ? [0] : ;
Due to which it throws the error since there's something missing after : and you probably don't want your code to be transpilled to this.
Thanks to Certain Performance's answer I learned new things about typescript especially the tool https://www.typescriptlang.org/play/index.html .
ECMA 262 (2020) which I am testing on Edge Chromium 84 can execute the Optional Chaining operator without TypeScript transpiler:
// All result are undefined
const a = {};
console.log(a?.b);
console.log(a?.["b-foo-1"]);
console.log(a?.b?.());
// Note that the following statements throw exceptions:
a?.(); // TypeError: a is not a function
a?.b(); // TypeError: a?.b is not a function
CanIUse: Chrome 80+, Firefox 74+
After a bit of searching the new page in the official documentation, it was discovered.
You need to put a . after the ? to use optional chaining.
So it will be so,
myArray.filter(x => x.testKey === myTestKey)?.[0]
Used only ? Makes the compiler think that you are trying to use a conditional operator (then it causes an error because it doesn't see a : later)
It's not necessary that the function is inside the object, you can run a function using optional chaining also like this:
someFunction?.();
If someFunction exists it will run, otherwise it will skip the execution and it will not error.
This technique actually is very useful especially if you work with reusable components and some components might not have this function.
Well, even though we figured out the correct syntax, the code doesn't make much sense to me.
The optional chaining in the code above is making sure, that the result of myArray.filter(x => x.testKey === myTestKey) is not null and not undefined (you can have a look at the TS output). But it is not possible anyway, because the result of the filter method is always an array. Since JavaScript doesn't throw "Array bounds exceeded", you are always safe when you try to access any index - you will get undefined if this element doesn't exist.
More example to make it clear:
const myArray: string[] = undefined
console.log(myArray.filter(x => x)?.[0]) //throws Cannot read property 'filter' of undefined
//in this example the optional chaining protects us from undefined array
const myArray: string[] = undefined
console.log(myArray?.filter(x => x)[0]) //outputs "undefined"

JS optional chaining clarification

I found a really great use case for optional chaining in my react project. I have used it slightly before but this line of code has made me a bit confused.
I have a table that is sortable.
One of the columns shows success or failure depending on if failed_date is null or a date.
sortedRows = sortedRows.sort((a, b) => a?.failed_date?.localeCompare(b?.failed_date));
But What confuses me is which value does the '?.' check is nullish?
Does a.failed_date?.localeCompare() check if failed_date?. is null/undefined or does it check if ?.localeCompare() is null/undefined?
Same with b?.failed_date is it checking b to be nullish? or failed_date.
I think My confusion comes from looking at the Documentation
Because arr?.[50] checks if element 50 is nullish but obj.method?.() checks if the method is not nullish?
Looking at this second block of code (which I believe is now correct) a.failed_date may be nullish and won't do localeCompare if a.failed_date is null?
But what if a.failed_date is not null but b.failed_date is null?
Does localeCompare not care? I haven't gotten an error but I was using localeComapre(b?.failed_date)
sortedRows = sortedRows.sort((a, b) => a.failed_date?.localeCompare(b.failed_date));
Let's say you define variable like below
const variable = { value: 'test' };
then you want to access variable?.value it equals variable === null || variable === undefined ? undefined : variable.value.
Same with array.
Check typescript playground and see js output https://www.typescriptlang.org/play?#code/MYewdgzgLgBAhgJwXAnjAvDA2gXQNwBQBiyKA-AHRYCsADDkA
Basically, the ? in that context means optional chaining.
How it works is, for example, if you define an object like below, and you want to try and access the views property, it will throw an error.
const obj = { website: "stackoverflow.com", };
console.log(obj.views.toFixed(0)); // Error!
This is because you are trying to access a method off of undefined, which doesn't have anything.
To avoid this error, we can use optional chaining, like below.
const obj = { website: "stackoverflow.com", };
console.log(obj?.views?.toFixed(0)); // undefined
Optional chaining tells JavaScript to complete the method/read the key, but if it doesn't exist, instead of throwing an error, just return undefined.
It also works with arrays; the same way! If, say, index 78 doesn't exist, instead of throwing an error, it will just return undefined.
const arr = [1, 2, 3];
console.log(arr?.[78]?.toString());
To elaborate here, it is possible to stack multiple optional chaining operators as seen in OP's code a?.failed_date?.localeCompare(b?.failed_date)
In these cases, it is not a question of which object key value will be checked. The code will be evaluated from left to right and if any of the object values are nullish then undefined will be returned.
Refer to the documentation for further understanding
MDN Optional Chaining

JSON stringify behaving differently on two different chrome tabs [duplicate]

I'm trying to figure out what's gone wrong with my json serializing, have the current version of my app with and old one and am finding some surprising differences in the way JSON.stringify() works (Using the JSON library from json.org).
In the old version of my app:
JSON.stringify({"a":[1,2]})
gives me this;
"{\"a\":[1,2]}"
in the new version,
JSON.stringify({"a":[1,2]})
gives me this;
"{\"a\":\"[1, 2]\"}"
any idea what could have changed to make the same library put quotes around the array brackets in the new version?
Since JSON.stringify has been shipping with some browsers lately, I would suggest using it instead of Prototype’s toJSON. You would then check for window.JSON && window.JSON.stringify and only include the json.org library otherwise (via document.createElement('script')…). To resolve the incompatibilities, use:
if(window.Prototype) {
delete Object.prototype.toJSON;
delete Array.prototype.toJSON;
delete Hash.prototype.toJSON;
delete String.prototype.toJSON;
}
The function JSON.stringify() defined in ECMAScript 5 and above (Page 201 - the JSON Object, pseudo-code Page 205), uses the function toJSON() when available on objects.
Because Prototype.js (or another library that you are using) defines an Array.prototype.toJSON() function, arrays are first converted to strings using Array.prototype.toJSON() then string quoted by JSON.stringify(), hence the incorrect extra quotes around the arrays.
The solution is therefore straight-forward and trivial (this is a simplified version of Raphael Schweikert's answer):
delete Array.prototype.toJSON
This produces of course side effects on libraries that rely on a toJSON() function property for arrays. But I find this a minor inconvenience considering the incompatibility with ECMAScript 5.
It must be noted that the JSON Object defined in ECMAScript 5 is efficiently implemented in modern browsers and therefore the best solution is to conform to the standard and modify existing libraries.
A possible solution which will not affect other Prototype dependencies would be:
var _json_stringify = JSON.stringify;
JSON.stringify = function(value) {
var _array_tojson = Array.prototype.toJSON;
delete Array.prototype.toJSON;
var r=_json_stringify(value);
Array.prototype.toJSON = _array_tojson;
return r;
};
This takes care of the Array toJSON incompatibility with JSON.stringify and also retains toJSON functionality as other Prototype libraries may depend on it.
Edit to make a bit more accurate:
The problem key bit of code is in the JSON library from JSON.org (and other implementations of ECMAScript 5's JSON object):
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
The problem is that the Prototype library extends Array to include a toJSON method, which the JSON object will call in the code above. When the JSON object hits the array value it calls toJSON on the array which is defined in Prototype, and that method returns a string version of the array. Hence, the quotes around the array brackets.
If you delete toJSON from the Array object the JSON library should work properly. Or, just use the JSON library.
I think a better solution would be to include this just after prototype has been loaded
JSON = JSON || {};
JSON.stringify = function(value) { return value.toJSON(); };
JSON.parse = JSON.parse || function(jsonsring) { return jsonsring.evalJSON(true); };
This makes the prototype function available as the standard JSON.stringify() and JSON.parse(), but keeps the native JSON.parse() if it is available, so this makes things more compatible with older browsers.
I'm not that fluent with Prototype, but I saw this in its docs:
Object.toJSON({"a":[1,2]})
I'm not sure if this would have the same problem the current encoding has, though.
There's also a longer tutorial about using JSON with Prototype.
This is the code I used for the same issue:
function stringify(object){
var Prototype = window.Prototype
if (Prototype && Prototype.Version < '1.7' &&
Array.prototype.toJSON && Object.toJSON){
return Object.toJSON(object)
}
return JSON.stringify(object)
}
You check if Prototype exists, then you check the version. If old version use Object.toJSON (if is defined) in all other cases fallback to JSON.stringify()
Here's how I'm dealing with it.
var methodCallString = Object.toJSON? Object.toJSON(options.jsonMethodCall) : JSON.stringify(options.jsonMethodCall);
My tolerant solution checks whether Array.prototype.toJSON is harmful for JSON stringify and keeps it when possible to let the surrounding code work as expected:
var dummy = { data: [{hello: 'world'}] }, test = {};
if(Array.prototype.toJSON) {
try {
test = JSON.parse(JSON.stringify(dummy));
if(!test || dummy.data !== test.data) {
delete Array.prototype.toJSON;
}
} catch(e) {
// there only hope
}
}
As people have pointed out, this is due to Prototype.js - specifically versions prior to 1.7. I had a similar situation but had to have code that operated whether Prototype.js was there or not; this means I can't just delete the Array.prototype.toJSON as I'm not sure what relies on it. For that situation this is the best solution I came up with:
function safeToJSON(item){
if ([1,2,3] === JSON.parse(JSON.stringify([1,2,3]))){
return JSON.stringify(item); //sane behavior
} else {
return item.toJSON(); // Prototype.js nonsense
}
}
Hopefully it will help someone.
If you don't want to kill everything, and have a code that would be okay on most browsers, you could do it this way :
(function (undefined) { // This is just to limit _json_stringify to this scope and to redefine undefined in case it was
if (true ||typeof (Prototype) !== 'undefined') {
// First, ensure we can access the prototype of an object.
// See http://stackoverflow.com/questions/7662147/how-to-access-object-prototype-in-javascript
if(typeof (Object.getPrototypeOf) === 'undefined') {
if(({}).__proto__ === Object.prototype && ([]).__proto__ === Array.prototype) {
Object.getPrototypeOf = function getPrototypeOf (object) {
return object.__proto__;
};
} else {
Object.getPrototypeOf = function getPrototypeOf (object) {
// May break if the constructor has been changed or removed
return object.constructor ? object.constructor.prototype : undefined;
}
}
}
var _json_stringify = JSON.stringify; // We save the actual JSON.stringify
JSON.stringify = function stringify (obj) {
var obj_prototype = Object.getPrototypeOf(obj),
old_json = obj_prototype.toJSON, // We save the toJSON of the object
res = null;
if (old_json) { // If toJSON exists on the object
obj_prototype.toJSON = undefined;
}
res = _json_stringify.apply(this, arguments);
if (old_json)
obj_prototype.toJSON = old_json;
return res;
};
}
}.call(this));
This seems complex, but this is complex only to handle most use cases.
The main idea is overriding JSON.stringify to remove toJSON from the object passed as an argument, then call the old JSON.stringify, and finally restore it.

In ES2015 is there a way to directly access object properties without checking for undefined?

In ES5 whenever I want to get some property I need to first check that it exists like this:
if (typeof floob.flib !== 'undefined') {
// do something
}
even worse is that for nested properties you have to manually check for the existence of every property down the dotted path.
Is there a better way to do this in ES2015?
If it is just a single depth property name - you don't need typeof, you may just compare the value with undefined.
Your solution is prone to false negatives: it may think there is no a property with a given name, while there is one. Example: var o = { foo: undefined };
If you need to check if the path exists in the nested objects - you still need to implement recursion/loop/or use any library that does either for you.
ES2015 did not bring anything new to solve this problem easier.
If you have lodash available, you can use _.get(obj, path, defaultValue) (https://lodash.com/docs#get)
By using typeof,
typeof floob.flib === 'undefined'
equals to,
floob.flib === undefined
I assume you want to check whether floob.flib has a value, and if it does, you want to perform an operation with it.
However, in JS, there's almost simpler way to achieve this.
E.g.
if (floob.flib) {
// 'floob.flib' is NOT 'null', 'empty string', '0', false or 'undefined'
}
This also works well if you want to assign variable with ternary ( ?: ) operators.
var str = floob.flib ? 'exists' : 'does not exist';
Or even using logical OR ( || )
var str = floob.flib || '<= either null, empty, false, 0 or undefined';
Note that unless floob.flib does not produce ReferenceError exception, the code above should work just fine.

Javascript equivalent of Rails try method

In Rails I can do this:
x = user.try(:name)
this method returns nil if user is nil else user.name. Here name is a method defined on the user object.
I know it can be done using if..then..else in Javascript but is there an equivalent compact method to do the same in Javascript?
Googling points to Javascript's try command which is not what I am looking for.
You can use optional chaining
Examples:
// Access Properties
user?.name; // user might be null/undefined
user.name?.firstName // user will be available however name is not guaranteed.
// Access array values
addresses?.[index]; // array addresses might be undefined
// May be function??
user.getGeolocation?.(); // If the function exists execute it.
Slightly unrelated but something around handling of null/undefined is another feature called Nullish coalescing operator ??
// Example:
function foo(input) // some Array as input {
//.. Some stuff
return [input??[]].concat(bar); //if input is empty ignore and concat bar on an empty array and return.
}
//----
const defaultVal = 'someVal';
...
const val = this.someObj.prop1??defaultVal;
Below is the outdated solution prior to Optional chaining became native to Javascript:
You can do this way, as there is no built in way of doing that:
var x = (user || {}).name;
If user is not defined/null you will get undefined
If user is defined you will get the name property (which may be set or undefined).
This won't break the script if user is not defined (null).
But user variable has to be declared some where in the scope, even though its value is not defined. Otherwise you will get the err saying user is not defined.
Similarly if is in global scope then you can explicitly check for this variable as a property of global scope, to avoid the error as mentioned above
ex:
var x = (window.user || {}).name; // or var x = (global.user || {}).name;
For safe execution of functions,
var noop = function(){}; //Just a no operation function
(windowOrSomeObj.exec || noop)(); //Even if there is no property with the name `exec` exists in the object, it will still not fail and you can avoid a check. However this is just a truthy check so you may want to use it only if you are sure the property if exists on the object will be a function.
Optional Chaining Operator
Is a new proposal for ECMAScript.
It is in an early stage but we can start using it with babel.
This is the problem:
const person = {name: 'santiago'}
let zip = person.address.zip // Cannot read property 'zip' of undefined
This is how it works:
const person = {name: 'santiago'}
let zip = person?.address?.zip // undefined
To start using it we need babel alpha 7:
npm install --save-dev babel-cli#7.0.0-alpha.19
npm install --save-dev babel-plugin-transform-optional-chaining#^7.0.0-alpha.13.1
And we need to add the plugin to our .babelrc
{
"plugins": ["transform-optional-chaining"]
}
Adam Bene Medium Post which explains how to use it and another use cases
You can use logical AND (&&) operator. It works differently than in most of the languages because its result is not a boolean. For example:
const x = user && user.name;
The table below shows all possible outcomes of using this operator:
+--------------+-----------------------+
| user | x = user && user.name |
+--------------+-----------------------+
| undefined | undefined |
| null | null |
| {} | undefined |
| {name:''} | '' |
| {name:'Jon'} | 'Jon' |
+--------------+-----------------------+
Isn't that the same as what this gives you in Javascript:
user['name']
user.name
This returns undefined instead of nil but that's equivalent in most cases. I think the reason Rails needs the try method is because you cannot access instance variables unless they are made accessible.
To add to #PSL's approach, I usually do something like:
var x = user || {},
y = x.name || {},
z = y.first_name;
In Rails4, try does this:
Invokes the public method whose name goes as first argument just like public_send does, except that if the receiver does not respond to it the call returns nil rather than raising an exception.
In Rails3, try is like try! in Rails4 and that's the same as try except that it complains if the object doesn't understand the method you're trying to call. The original intent of try was to hide a bunch of nil checks so that you could say:
o.try(:m)
instead of
o.nil?? nil : o.m
The Rails4 version also hides a check if the object understands the method you want to call.
There's no special syntax for this in JavaScript but you can sweep the ugliness into a function.
The Rails4 version of try would look like this function in JavaScript:
function rtry(obj, m) {
if(obj == null)
return null;
if(typeof obj[m] === 'function')
return obj[m].apply(obj, [].slice.call(arguments, 2));
return null;
}
and you'd say things like x = rtry(obj, 'method_name', arg1, arg2, ...) and x would be null if obj didn't understand method_name (including if obj is null or undefined).
Demo: http://jsfiddle.net/ambiguous/BjgjL/
A Rails3 version is simple, that's just a null check:
function rtry(obj, m) {
if(obj == null)
return null;
return obj[m].apply(obj, [].slice.call(arguments, 2));
}
and JavaScript itself will raise an exception of obj doesn't have an m method. This version is equivalent to the "is it a function" version of CoffeeScript's ? existential operator.
Demo: http://jsfiddle.net/ambiguous/FQCS2/
As usual, things might not work out so well if you're dealing with native methods rather than methods that you've written in JavaScript. typeof (6).toString might be 'function' in one JavaScript environment but I don't think it is guaranteed to always be 'function' everywhere. The primary use (i.e. hide null and undefined checks) should work everywhere though.
I don't think so. Why not roll your own? Not exactly equivalent but does the job.
function tryMe(obj) {
return obj === null || obj === undefined ? null : obj;
}

Categories