Optional chaining is great: it allows us to safely access object properties without worrying if they exist:
const user: {
name: "Bob"
}
const val = user?.pet?.name;
Rather than crashing if pet
doesn't exist on user
, it'll simply return undefined
.
While this is promising, optional chaining is in stage 4 of proposal for the ECMAScript standard, and is therefore not guaranteed to be available unless specifically accommodated in a transpiler you're using.
Rolling Your Own
The good news is we can fairly easily roll our own optional chaining-like function! Let's call it deepGet
:
function deepGet(obj, ...props) {
try {
return props.reduce((acc, el) => acc[el], obj);
} catch (e) {
return undefined;
}
}
We can see that we try to iterate over all props
in our reduce
function. If an error is ever thrown, we just return undefined
! Let's see it in action:
const obj = {
user: {
name: "Joe",
age: 20,
pets: [
{
name: "Daffodil",
type: "dog",
toys: [
{
name: "Toughy",
price: 1999
}
]
}
]
}
};
function deepGet(obj, ...props) {
try {
return props.reduce((acc, el) => acc[el], obj);
} catch (e) {
return undefined;
}
}
console.log(deepGet(obj, "user", "pets", 0, "toys", 0, "price"));
// 1999
console.log(deepGet(obj, "user", "dogs", 0, "toys", 0, "price"));
// undefined
And there you have it, your own safe deepGet
function!
Top comments (12)
I know another cool trick, using
Proxy
.The syntax is very similar to native.
This is far better than the one provided by OP . Thanks
Until you have a property named
_
that you can't access anymore.Thanks for pointing out, feel free to try this.
This API looks very sexy, don't get me wrong, but it has some flaws:
But if you are using an object from a trusted source, then your solution is very elegant.
This claim is false, no matter the size of code-bases, you always have to know where to access the right field in that JSON. Knowing the JSON field location already avoided a bad ending.
The origin of the problem above comes from the untrusted source, not my provided solution. Optional chaining can't solve it either.
Reminder: All of my solutions do avoid crashing from accessing bad indices.
Optional chaining is the topic of this article, which means no exception is thrown from accessing a bad index. Have you tried to access a bad index from my solutions yet? It behaves as expected.
Hi Nick!
Awesome usage of the rest operator in this context, I really like this API. this looks more natural than using a string or an array for accessing a chain of properties.
For the return type, I would have returned
null
instead ofundefined
. From my understanding,null
represents the intentional absence of a value, whileundefined
is a declared value that has not been provided. But I may be wrong on this one. What do you think? Should this function returnnull
rather thanundefined
?Anyway, good take on this one!
Here is an alternative with JSONPath Expressions, a tool for selecting data in a JSON structure by an expression kinda like XPath does for XML. Here is an expression example: "
$.user.pets[0].toys[0].price
" - to select the price for the first toy for the first pet for the user. A JavaScript object is a perfect fit for a JSONPath expression evaluation where the object contains nested arrays, dictionaries, and strings.The npm package jsonpath-plus is a popular JSON Path evaluation engine though pick any one in the world you like! JSONPath probably wasn't invented by a computer. It's very user friendly.
lodash .get() is my second most favorite alternative. Kinda looks like some of use of lodash is going to get deleted when we all can use optional chaining. Which isn't a bad thing.
Nice! good article :D
Oh I've got one:
Nifty, and doesn't re-invent the wheel.
Shouldn't it be:
Instead of:
?
Plus, you don't want to import the whole 69kb of Lodash just for this matter. Since you are using ECMAScript Modules, you should use named imports instead.
Niftier, and does not import the unnecessary functions just to use the
get
helper.Nice one! Thanks for sharing!