In Typescript, I need to set a nested field in a type that similar to this one:
type NestedOptional = {
a?: { b?: { c?: { d?: number; }; }; };
};
This requires a lot of boilerplate code to initialize all the non-leaves objects when needing to set the d
leaf value. How can I avoid this?
function fillLeafValue(obj: NestedOptional, value: number) {
// how can I avoid this boilerplate code when setting a value,
// as does the JS optional chaining (?.) for getting values?
if (obj.a == undefined) obj.a = {};
if (obj.a.b == undefined) obj.a.b = {};
if (obj.a.b.c == undefined) obj.a.b.c = {};
obj.a.b.c.d = value;
}
I tried something like this:
// *** does not work: issue with generics recursion?
function ensure<T>(obj: T) {
return function <K extends keyof T>(key: K, ctor: () => T[K]) {
if (obj[key] == undefined) obj[key] = ctor();
return ensure(obj[key]);
};
}
// I wish I could use it like the following
const empty = () => ({}); // how to init the objects
function fillLeafValue2(obj: NestedOptional, value: number) {
ensure(obj)("a", empty)("b", empty)("c", empty);
// ^ Argument of type 'string' is not assignable to parameter of type 'never'.
}
Also tried things like ts-get-set npm package but it's not strict enough: it allows setting "a.z.c"
without an error for instance!
FWIW, I am actually using Immer JS with the produce
method, but I still need to fill all optional layers of the draft.
Any advice? It looks like a pretty common case...
It's as simple as that (no proxy, no lib...):
(((obj.a ??= {}).b ??= {}).c ??= {}).d = value;
I am eventually providing the answer to my own question because after 4 months, neither @VLAZ nor @jcalz have added an answer that I could officially accept. Thanks to both of them for providing me the answer in the question comments 🙏 All credit to them!