WoJ
WoJ

Reputation: 29987

How to set the property of an yet-undefined element and create it on the fly?

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

Answers (3)

João Vilaça
João Vilaça

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

Mark Walters
Mark Walters

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.

JSFiddle

Upvotes: 1

Kroltan
Kroltan

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

Related Questions