$ snippet get-set-by-path
Get and set nested values by path
get(obj, 'a.b[0].c') and set(obj, 'a.b.c', 1) that never throw on missing keys and create intermediate objects on write.
/** "a.b[0].c" or ["a","b",0,"c"] → normalized key array */
function toPath(path) {
if (Array.isArray(path)) return path;
return path.replace(/\[(\w+)\]/g, ".$1").split(".").filter(Boolean);
}
/** get(obj, "a.b.0.c", fallback) — undefined-safe */
function get(obj, path, fallback) {
let cur = obj;
for (const key of toPath(path)) {
if (cur == null) return fallback;
cur = cur[key];
}
return cur === undefined ? fallback : cur;
}
/** set(obj, "a.b.0.c", value) — mutates and returns obj; creates {} or [] as needed */
function set(obj, path, value) {
const keys = toPath(path);
let cur = obj;
keys.forEach((key, i) => {
if (i === keys.length - 1) { cur[key] = value; return; }
if (cur[key] == null || typeof cur[key] !== "object")
cur[key] = /^\d+$/.test(keys[i + 1]) ? [] : {};
cur = cur[key];
});
return obj;
}
/** has(obj, "a.b") — true only if the full path exists */
function has(obj, path) {
let cur = obj;
for (const key of toPath(path)) {
if (cur == null || !(key in Object(cur))) return false;
cur = cur[key];
}
return true;
}How it works
Optional chaining (obj?.a?.b) covers the static case. These helpers are for when the path is data — a column name from config, a field in a form builder, a key the user typed. get walks the path and bails out at the first null, set builds the missing containers, choosing an array when the next key looks like an index.
Gotchas
- Never
setwith a user-controlled path on shared objects without blocking__proto__,constructorandprototypekeys — that's the prototype-pollution vulnerability. Add a guard if paths come from outside. getreturns the fallback forundefinedbut not fornull, matching lodash. Change the last line if you want both.- Keys containing dots can't be expressed in the string form; pass an array path instead.
Ad slot