Reputation: 834
I want to populate a dictionary of dictionaries in JavaScript.
As soon as I have a resident's address and name, I want to write it into the object, i.e.
addressCache[city][street][number] = name
However, if my dictionary doesn't contain a value for the city
's key yet, it's accessing a street
of undefined
since the dictionary does not exist yet.
My workaround at the moment is
adressCache = {}
function setAddressEntry(city, street, number, familyName) {
if(!adressCache[city]) {
adressCache[city] = {}
}
if(!adressCache[city][street]) {
adressCache[city][street] = {}
}
adressCache[city][street][number] = familyName
}
I don't think that's the right way of doing that. How can I improve this code? Or is it common to write a helper function for that?
Upvotes: 3
Views: 4709
Reputation: 29087
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);
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);
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);
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.
Upvotes: 2
Reputation: 628
I don't see any problem with your code. That is how you set a value for an object which don't have a proper path to a specific property.
some code improvisations:
adressCache = {}
function setAddressEntry(city, street, number, familyName) {
adressCache[city] = adressCache[city] || {};
adressCache[city][street] = adressCache[city][street] || {};
adressCache[city][street][number] = familyName;
}
Upvotes: 2
Reputation: 25406
It would be much easier and clean with Logical nullish assignment (??=)
let adressCache = {};
function setAddressEntry(city, street, number, familyName) {
adressCache.city ??= {};
adressCache.city.street ??= {};
adressCache.city.street.number = familyName;
}
setAddressEntry("gurugram", "1st street", 24, "SULTAN");
console.log(adressCache);
Upvotes: 6