Using a proxy
This can be generalised to any object by using a Proxy:
const infiniteChainHandler = {
get(target, prop, receiver) {
if (!(prop in target))
target[prop] = {};
const result = target[prop];
if (typeof result === "object" && result !== null) {
return new Proxy(result, infiniteChainHandler);
}
return result;
}
};
/* ... */
new Proxy(adressCache, infiniteChainHandler)[city1][street1][number1] = familyName1;
Demo:
const infiniteChainHandler = {
get(target, prop, receiver) {
if (!(prop in target))
target[prop] = {};
const result = target[prop];
if (typeof result === "object" && result !== null) {
return new Proxy(result, infiniteChainHandler);
}
return result;
}
};
/* Example usage: */
const adressCache = {};
const city1 = "Oz";
const street1 = "Yellow Brick Road";
const number1 = "1";
const familyName1 = "Gale";
new Proxy(adressCache, infiniteChainHandler)[city1][street1][number1] = familyName1;
const street2 = "Wizard boulevard";
const number2 = "12";
const familyName2 = "The Wizard";
new Proxy(adressCache, infiniteChainHandler)[city1][street2][number2] = familyName2;
new Proxy(adressCache, infiniteChainHandler)["something"]["else"] = "here";
new Proxy(adressCache, infiniteChainHandler)
.any
.very
.long
.and
.nested
.path
.is
.also
.supported
.using
.this = "approach";
console.log(adressCache);
- Wrap the object in the proxy.
- Use the get trap and when reading any property, create it if it's not there and set it to an empty object.
- If the returned value would be an object, wrap it in a Proxy object again using the same handler.
- If the return value is anything else, just return it.
This allows you to chain any amount of properties and have the path automatically created.
Note that the above wraps addressCache
into the proxy every time to keep the variable itself a simple object. If you start with const adressCache = new Proxy(adressCache, infiniteChainHandler)
it makes extracting the data out harder.
To avoid having to write new Proxy
all the time, you can use something like this for convenience:
/* library code */
const wrap = handler => obj =>
new Proxy(obj, handler);
/* /library code */
/* ... */
const chainable = wrap(infiniteChainHandler);
/* ... */
chainable(adressCache)[city1][street1][number1] = familyName1;
/* library code */
const wrap = handler => obj =>
new Proxy(obj, handler);
/* /library code */
const infiniteChainHandler = {
get(target, prop, receiver) {
if (!(prop in target))
target[prop] = {};
const result = target[prop];
if (typeof result === "object" && result !== null) {
return new Proxy(result, infiniteChainHandler);
}
return result;
}
};
const chainable = wrap(infiniteChainHandler);
/* Example usage: */
const adressCache = {};
const city1 = "Oz";
const street1 = "Yellow Brick Road";
const number1 = "1";
const familyName1 = "Gale";
chainable(adressCache)[city1][street1][number1] = familyName1;
const street2 = "Wizard boulevard";
const number2 = "12";
const familyName2 = "The Wizard";
chainable(adressCache)[city1][street2][number2] = familyName2;
chainable(adressCache)["something"]["else"] = "here";
chainable(adressCache)
.any
.very
.long
.and
.nested
.path
.is
.also
.supported
.using
.this = "approach";
console.log(adressCache);
Use a function
This instead generalises the function that does the assignment itself to work with any object and any path length:
const assignTo = obj => (...path) => value => {
const lastProp = path.pop();
let target = obj;
for (const prop of path) {
if (!(prop in target))
target[prop] = {};
target = target[prop];
}
target[lastProp] = value;
}
/* ... */
const adressCache = {};
const addAdress = assignTo(adressCache);
/* ... */
addAdress (city1, street1, number1) (familyName1);
/* library code */
const assignTo = obj => (...path) => value => {
const lastProp = path.pop();
let target = obj;
for (const prop of path) {
if (!(prop in target))
target[prop] = {};
target = target[prop];
}
target[lastProp] = value;
}
/* /library code */
/* Example usage: */
const adressCache = {};
const addAdress = assignTo(adressCache);
const city1 = "Oz";
const street1 = "Yellow Brick Road";
const number1 = "1";
const familyName1 = "Gale";
addAdress (city1, street1, number1) (familyName1);
const street2 = "Wizard boulevard";
const number2 = "12";
const familyName2 = "The Wizard";
addAdress (city1, street2, number2) (familyName2);
addAdress ("something", "else") ("here");
addAdress (
"any",
"very",
"long",
"and",
"nested",
"path",
"is",
"also",
"supported",
"using",
"this") ("approach");
console.log(adressCache);
- Separate out the last property.
- Go through the entire path and assign an object if it's not there.
- Assign the value to the last property of the path provided.
This solution uses multiple arrow functions (with variable amount of arguments at the second step) for a bit of simplicity. You could also take the entire path and the value as parameters but then the calls will be uglier. This makes it clearer what is the path, what is the value.