Reputation: 29987
var x = {}
x['hello'] = {"what": "world"}
alert(x['hello'].what)
works because the element 'hello' is explicitly set.
I would like to define a proerty of a yet-not set element and create it on the fly, something like
var x = {}
x['hello']['what'] = "world"
This fais with TypeError: Cannot set property 'what' of undefined
.
Is there a way to handle such a case, similarly to using collections.defaultdict
in Python?
Upvotes: 1
Views: 90
Reputation: 621
Try using Object.setPrototypeOf(x, {"what": "world"})
. Does that work for you?
or using Object.setPrototypeOf({}, {"hello": {"what": "world"}})
for the "hello" case.
Upvotes: 0
Reputation: 12390
It's a bit crude and can probably be improved upon, but as a start you could use a function which splits a . separated string and creates the nested sub objects for you. Something like this works
function setObjValue(obj, nesting, value) {
var nestArr = Array.isArray(nesting) ? nesting : (nesting.indexOf(".") > -1) ? nesting.split(".") : [nesting],
len = nestArr.length,
i = 0,
cur = obj;
for (; i < len; i++) {
if (i === len-1) {
cur[nestArr[i]] = value;
} else {
if (!cur[nestArr[i]]) cur[nestArr[i]] = {};
cur = cur[nestArr[i]];
}
}
}
Then to use
var a = {};
setObjValue(a, "hello.what", "world");
//OR
setObjValue(a, ["hello", "what"], "world");
console.log(a); //{hello:{ what: "world" }};
UPDATE: I've just updated the function above and the fiddle to handle objects with those keys already set and it will also accept the nesting argument as an array.
Upvotes: 1
Reputation: 5156
You can use a Proxy
, but note that support is not global.
There's also some edge cases that must be taken into account. Below is a very simple (and potentially naïve) implementation of a defaultdict
in JS:
function makeDefaultDictFor(obj) {
// Protect against infinite recursion for
// intentionally nonexistng properties
let bannedKeys = [Symbol.toPrimitive, Symbol.iterator, "toJSON"];
// Create a proxy that returns a new object for any missing keys.
// The returned object also has the same behaviour, so you can
// nest infinitely deep.
return new Proxy(obj, {
get: (target, prop) => {
if (bannedKeys.indexOf(prop) >= 0) return;
// Return existing values when possible
if (prop in target) return target[prop];
// Make a proxy like this one for undefined keys.
let newProxy = makeDefaultDictFor({});
// Save the object as a property in this object.
target[prop] = newProxy;
return newProxy;
}
});
}
let obj = makeDefaultDictFor({});
obj.hello.world = "!";
document.write("<pre>");
document.write(JSON.stringify(obj));
document.write("</pre>");
Upvotes: 0