Hi everyone,
I'm pretty sure I've already seen this idea somewhere, but I'm not able to find anything about it, so here I go (and sorry if this is duplicate).
The issue
There is a pattern which is encountered quite often: setting properties of a nested object, where that nested object has potentially not been initialized yet.
myObject.sub.value = 3 // `sub` could potentially be undefined
Here are some of the "cleanest" ways I could think of for solving this problem today:
// Using the nullish coallescing operator
myObject.sub ??= {};
myObject.sub.value = 3;
// Using ternary condition
myObject.sub = !!myObject.sub ? 3 : { value: 3};
// Using lodash
_.set(myObject, ['sub'], 3);
The major drawback of the first solution is that it is a two-step solution, and we are repeating the object name twice.
The second solution is a oneliner, but is much less readable, and we now repeat the object name AND the assigned value twice.
And for the third one, we have to rely on an external library to achieve our goal.
The solution
In order to improve code (and developer life) quality in such cases, I propose to reuse the optional chaining ?.
operator, but on the assignment side:
myObject.sub?.value = 3;
This would desugar to something like:
if (typeof myObject.sub != 'object') myObject.sub = {};
myObject.sub.value = 3;
The bracket notation would also be supported:
const myKey = 'value';
myObject.sub?.[myKey] = 3;
would desugar to:
const myKey = 'value';
if (typeof myObject.sub != 'object') myObject.sub = {};
myObject.sub[myKey] = 3;
This syntax would come very handy, especially when we have to deal with deep nested maybe-undefined properties:
myObject.a?.sub?.property?.value = 3;
Some implementation notes:
- If a nested property is already defined but is not and object, it will be overridden (if writable)
const myObject = {};
myObject.a = 3;
myObject.a?.value = 3;
// myObject === { a: { value: 3 }}
const myObject = {};
Object.assignProperty(myObject, 'a', { writable: false, value: 3 });
myObject.a?.value = 3;
// myObject === { a: 3 }
- Using
?.
on the first object should not be allowed, as it could lead to errors / unintuitive behaviors (especially if the variable is constant and/or set to a primitive).
myObject?.value = 3 // SyntaxError
What if a nested property should be something other than an object?
A question that might raise is: what if a nested property should not be a simple object, but an array, or a custom class object, or even any possible value you'd like? Would there be a syntax to support such cases?
In my opinion, I don't think this would be a good idea to include these particular cases (at least not for this proposal).
There are to main reasons for this:
- First, I can't come with a clean, readable and intuitive solution for such a use case (if you can find one, please feel free to share it!)
- Secondly, initializing custom objects on the fly could be quite a complex operation, which might require proper setup instead of using a shorthand syntax. So I'm not sure it would be a good thing to have a dedicated syntax for that anyway.
So in the end I think it would be better to focus first on the initial problem, which is simpler and more common, and then extended it in another proposal if this first one is somehow successful.
Thoughts?
Please share your thoughts! :)
(Also if you can come with a better name for "optional chaining assignment" it would be great!)