Repeatedly OR it with an empty object
I can't live without Elvis when writing HTML that references properties/subproperties that may or may not exist.
We can't write
this.myForm?.name?.first_name
I guess the people in charge of ES are probably spending most of their time actually writing JS, and not writing bits of front-end HTML that accesses object properties, hence their bemusement as to why anyone would need safe navigation.
They argue that "Nobody needs it", "It allows errors to be silent and could thereby bite you later" and "How would you interpret x?(y)
?"
My solution, while I wait for a future ES specification to include the Elvis operator, is as follows:
(((this.myForm || {}).name || {}).first_name )
In short, at every stage that a value could be undefined
, you OR
it with a {}
, so that the undefined becomes an empty object. Once you put that whole thing into parentheses, you are safe to try to extract a property of it, since any property you try to extract from {}
is simply undefined
rather than an actual error.
It does get a bit ugly when you have multiple tiers, but in many cases it is less ugly than having to use a series of &&
to progressively step down through the levels of the object.
For array access, OR it with an empty array
If navigating into an array, for example if the form had many names and you wanted to select the first, the approach is similar:
((((this.myForm || {}).names || [])[0] || {}).first_name )
What happens if something in the chain is 0 or ""?
Suppose you were expecting to read a value "John" from this.myForm.names[0].first_name
.
The typical situation that would normally trigger an error is where there is simply no object this.myForm
. Instead of having an error, the formula I described above would work, and be interpreted as {}.first_name
, which is undefined
(but not an error).
Now imagine a hostile person has sneakily set this.myForm
to 0 or "" or {}. Will we now see an error? No, because the values 0 or "" are considered falsy by Javascript, and {} is truthy but is itself {}, so this.myForm || {}
evaluates to {}, and the remainder of the chain falls through to defaults as before, and the method still works, returning undefined
rather than an error.
Example
console.log("Each example shows first the result WITH the workaround, and then WITHOUT.")
console.log("a. This should work normally.")
a = { myForm:{ names:[ { first_name:"John", surname:"Smith"}]} };
console.log(a.myForm.names[0].first_name)
console.log ((((a.myForm || {}).names || [])[0] || {}).first_name )
console.log("b. We have removed the first_name property. This still works.")
b = { myForm:{ names:[ { surname:"Smith"}]} };
console.log(b.myForm.names[0].first_name)
console.log ((((b.myForm || {}).names || [])[0] || {}).first_name )
console.log("c. We have removed the entire 'names' array. This is fixed by the workaround, but gives an error if run without the workaround.")
c = { myForm:{ } };
console.log ((((c.myForm || {}).names || [])[0] || {}).first_name )
console.log(c.myForm.names[0].first_name)
console.log("d. Now the whole root object is empty. Again the workaround gets around this, but without the workaround, we get an error.")
d = { };
console.log ((((d.myForm || {}).names || [])[0] || {}).first_name )
console.log(d.myForm.names[0].first_name)