In order to avoid error when accessing deeply nested properties, I wrote a proxy-returning function:
const safe_access = obj =>
new Proxy(obj, {
get: (o, k) =>
o[k] == null
? safe_access({})
: typeof o[k] === 'object'
? safe_access(o[k])
: o[k] });
Here's an example:
const a = safe_access({});
a.x.y.z; // no TypeError 🎉
However in its current form safe_access
is unable to tell when it has reached the end of the path. Meaning that it cannot return undefined
to signify that the property truly doesn't exist. This also means that you cannot have default values:
const a = safe_access({});
a.x.y.z || 42; // not 42
const {x: {y: {z = 42}}} = a;
z; // not 42
How can my proxy object detect the end of a property lookup?
This answer more or less applies here, for the same reasons.
You can't detect the end of an access chain because nothing makes it any different from the preceding accesses. At runtime, the following code is effectively identical to let r = a.x.y.z
.
let r = a;
{
r = r.x;
r = r.y;
r = r.z
}
If you actually want to use this sort of safe navigation in code you're writing, the best is to use the optional chaining (?.
) and nullish coalescing (??
) operators added to the Javascript standard in 2020. They provide a neater, less confusing way to do this sort of thing, and are supported by all modern browsers.
let r = a?.x?.y?.z ?? 42;
If you need to support legacy browsers, you can get these operators from these two Babel plugins: ([1], [2]).
However, if you really want to implement "safe" access yourself, there are a few tricks you can use to get around this.
One trick, that probably requires the least additional work is to reserve one name to indicate the end of the safe access chain. (I've done something similar in Python in the past.)
function safe_access(value) {
let obj = (typeof(value) === 'object') ? value : {};
return new Proxy(obj, {
value: value,
get: function(target, property) {
if (property === "$")
return this.value;
else
return safe_access(target[property]);
}
});
}
let a = {'x': {'y': 123}};
// a.x.y is a proxy object
a.x.y.$ === 123
a.x.y.z.$ === undefined
Another trick is detailed in this blog post.