This pairs the nested reader get with its write counterpart set. get(obj, 'a.b[0].c') safely reads a deep value; set(obj, 'a.b[0].c', v) writes one — creating any missing objects and arrays along the way, so you never have to hand-build the path first.
Implement both. get(obj, path, defaultValue) returns the value at path (a string like 'a.b[0].c' or an array of keys), or defaultValue if any step is missing. set(obj, path, value) writes value at path, building missing containers, and returns the mutated obj.
function get(obj, path, defaultValue) {} // read; defaultValue if missing
function set(obj, path, value) {} // write; creates missing containers
get({ a: { b: [{ c: 7 }] } }, 'a.b[0].c'); // 7
get({ a: 1 }, 'a.b.c', 'fallback'); // 'fallback'
set({}, 'a.b.c', 1); // { a: { b: { c: 1 } } } — objects created
set({}, 'a[0].b', 1); // { a: [{ b: 1 }] } — array created for [0]
'a.b[0].c', or an array ['a', 'b', 0, 'c'].set creates containers — for a missing step, make an array if the next segment is a numeric index, otherwise a plain object.set mutates and returns obj — it writes in place; the return value is the same object for chaining.a.c must not disturb an existing a.b.get is safe — a missing intermediate returns defaultValue, never throws.