115

Is there an easy way to automatically add properties to objects if they don't already exist?

Consider the following example:

var test = {}
test.hello.world = "Hello doesn't exist!"

This doesn't work because hello isn't defined.

The reason why I'm asking this is because I have some existing objects for which I don't know if they allready have hello or not. I actually have a lot of these objects in different parts of my code. It is very annoying to always check if hello exists and if it doesn't create a new object like:

var test = {}
if(test.hello === undefined) test.hello = {}
test.hello.world = "Hello World!"

Is there a way to automatically create an object like hello in this example?

I mean something like that in php:

$test = array();  
$test['hello']['world'] = "Hello world";   
var_dump($test);

Output:

array(1) {
  ["hello"] => array(1) {
    ["world"] => string(11) "Hello world"
  }
}

Ok it's an array but in js arrays it is the same problem as with objects.

ctrlaltdeleon
  • 356
  • 3
  • 14
Marcel Gwerder
  • 8,353
  • 5
  • 35
  • 60
  • function existcheckthingy(x, y, z){ if(x === undefined) x = {}; x.y = z; } used as existcheckthingy(test.hello, world, "Hello doesn't exist!"); – bobbybee Jul 14 '13 at 21:30
  • 2
    @bobbybee That won't work. It will create a new object `x` in the scope of the `existcheckthingy` function, but that will not then be attached to the `test` object. You can do better by using the "array-like" notation: `existcheckthingy(a,x,y,z) { if (a[x] === undefined) a[x] = {}; a[x][y] = z;}` – Jeff Jul 14 '13 at 21:36
  • Possible duplicate: http://stackoverflow.com/questions/7069584/js-build-object-path-in-property-assignment – Maxim Zhukov Jul 14 '13 at 21:40
  • @Jeff oops, forgot about that :3 – bobbybee Jul 14 '13 at 21:43
  • 3
    consider lodash 'set' – Rene Wooller Feb 17 '16 at 06:03

15 Answers15

163
var test = {};
test.hello = test.hello || {};
test.hello.world = "Hello world!";

If test.hello is undefined, it gets set to an empty object.

If test.hello was previously defined, it stays unchanged.

var test = {
  hello : {
    foobar : "Hello foobar"
  }
};

test.hello = test.hello || {};
test.hello.world = "Hello World";

console.log(test.hello.foobar); // this is still defined;
console.log(test.hello.world); // as is this.
Ayush
  • 41,754
  • 51
  • 164
  • 239
74

You can use the Logical nullish assignment (??=):

var test = {};
(test.hello ??= {}).world ??= "Hello doesn't exist!";
DexBG
  • 841
  • 5
  • 4
21

New object

myObj = {};

recursive function

function addProps(obj, arr, val) {

    if (typeof arr == 'string')
        arr = arr.split(".");

    obj[arr[0]] = obj[arr[0]] || {};

    var tmpObj = obj[arr[0]];

    if (arr.length > 1) {
        arr.shift();
        addProps(tmpObj, arr, val);
    }
    else
        obj[arr[0]] = val;

    return obj;

}

Call it with a dot notated string

addProps(myObj, 'sub1.sub2.propA', 1);

or with an array

addProps(myObj, ['sub1', 'sub2', 'propA'], 1);

and your object will look like this

myObj = {
  "sub1": {
    "sub2": {
      "propA": 1
    }
  }
};

It works with non-empty objects too!

columbus
  • 211
  • 2
  • 3
  • 1
    this actually works really neatly, and updates the correct value without breaking my object. – RozzA Apr 12 '17 at 01:02
  • For anyone implementing this in Typescript, the object's type can be defined as `type RecursiveObject = { [key: number | string]: RecursiveObject | any };` – IcyIcicle Jun 24 '22 at 02:48
7

You won't be able to do this without some sort of function, as JavaScript doesn't have a generic getter/setter method for objects (Python, for example, has __getattr__). Here's one way to do it:

function add_property(object, key, value) {
    var keys = key.split('.');

    while (keys.length > 1) {
        var k = keys.shift();

        if (!object.hasOwnProperty(k)) {
            object[k] = {};
        }

        object = object[k];
    }

    object[keys[0]] = value;
}

If you really want to, you could add it to the prototype of Object. You can call it like so:

> var o = {}
> add_property(o, 'foo.bar.baz', 12)
> o.foo.bar.baz
12
Blender
  • 289,723
  • 53
  • 439
  • 496
6

Here's a cool version with proxies:

const myUpsert = (input) => {
    const handler = {
        get: (obj, prop) => {
            obj[prop] = obj[prop] || {};
            return myUpsert(obj[prop]);
        }
    };
    return new Proxy(input, handler);
};

And you use it like this:

myUpsert(test).hello.world = '42';

This will add all the missing properties as empty objects, and leave the existing ones untouched. It's really just a proxied version of the classic test.hello = test.hello || {}, albeit much slower (See benchmark here.) But it's also much nicer to look at, especially if you'll be doing it more than one level deep. I wouldn't pick it for performance-heavy data crunching, but it's probably fast enough for a front-end state update (as in Redux).

Note that there's some implicit assumptions here:

  1. The intervening properties are either objects or non-existent. This will choke if test.hello is a string, for example.
  2. That you always want to be doing this for as long as you're using the Proxy instead of the original object.

These are pretty easily mitigated if you only use it in well-bounded contexts (like a reducer body) where there's little chance of accidentally returning the Proxy, and not much else you would want to do with the object.

John Neuhaus
  • 1,784
  • 20
  • 32
5

Well you could extend the prototype of Object with a function that return a property, but adds it first, if it doesn't exist:

Object.prototype.getOrCreate = function (prop) {
    if (this[prop] === undefined) {
        this[prop] = {};
    }
    return this[prop];
};

var obj = {};

obj.getOrCreate("foo").getOrCreate("bar").val = 1;
basilikum
  • 10,378
  • 5
  • 45
  • 58
  • 1
    this seems like a great solution however it breaks DataTables for some reason. Is messing with object internals considered a good idea normally? – Jason Lewis Mar 09 '17 at 02:41
  • This is why extending native objects can be a bad idea, because methods might overlap. – Tschallacka Mar 21 '17 at 08:34
2
var test = {}
if(!test.hasOwnProperty('hello')) {
    test.hello = {};
}
test.hello.world = "Hello World!"
Shawn31313
  • 5,978
  • 4
  • 38
  • 80
2

This will add a property hello whose value is {world: 'Hello world!'} to the test object, if it doesn't exist. If you have a lot of these objects, you can just iterate over them and apply this function. Note: uses lodash.js

var test = {};
_.defaults(test, { hello: {world: 'Hello world!'} });    

Which is actually a convenience method for saying:

var defaults = _.partialRight(_.assign, function(a, b) {
  return typeof a == 'undefined' ? b : a;
});        
defaults(test, { hello: {world: 'Hello world!'} });

Note: _.defaults uses loops to achieve the same thing as the second block.

P.S. Checkout https://stackoverflow.com/a/17197858/1218080

Community
  • 1
  • 1
holographic-principle
  • 19,688
  • 10
  • 46
  • 62
  • 1
    _.set({}, 'a.b.c.d', "asdf") will create '{"a":{"b":{"c":{"d":"asdf"}}}}', and might be more what he was looking for – Rene Wooller Feb 17 '16 at 06:03
2

let test = {};
test = {...test, hello: {...test.hello, world: 'Hello does exist!'}};
console.log(test);

When using the spread operator, the value can be undefined, it'll automatically create an object.

Nick
  • 11,483
  • 8
  • 41
  • 44
1

I've come up with something, really custom as well, but it works as far as I have tested.

function dotted_put_var(str,val) {
    var oper=str.split('.');
    var p=window;
    for (var i=0;i<oper.length-1;i++) {
        var x=oper[i];
        p[x]=p[x]||{};
        p=p[x];
    }
    p[oper.pop()]=val;
}

Then, a complex variable can be set like this, ensuring that every links will be created if not already:

dotter_put_var('test.hello.world', 'testvalue'); // test.hello.world="testvalue";

See this working FIDDLE.

Frederik.L
  • 5,522
  • 2
  • 29
  • 41
1

I use this:

Object.prototype.initProperty = function(name, defaultValue) {
  if (!(name in this)) this[name] = defaultValue;
};

You can later do f.e.:

var x = {a: 1};
x.initProperty("a", 2); // will not change property a
x.initProperty("b", 3); // will define property b
console.log(x); // => {a: 1, b: 3}
Michal Politzer
  • 313
  • 3
  • 9
1
var test = {}
test.hello.world = "Hello doesn't exist!"

This will throw an error obviously as you didn't defined the test.hello

Firstly you need to need define the hello key then inside you can assign any key. But if you want to create key if not exists then you can do following thing

test.hello = test.hello || {};

The above statement will create the test.hello object if not defined and if it is defined then it will assign the same value as it is previously

Now you can assign any new key inside the test.hello

test.hello.world = "Everything works perfect";

test.hello.world2 = 'With another key too, it works perfect';
VIKAS KOHLI
  • 8,164
  • 4
  • 50
  • 61
1

I think the easiest way is to use _.set from Lodash

 _.set({}, 'a[0].b.c', 4);
// => { a: [{ b: { c: 4 } }] }
0

I've made some changes on columbus's answer to allow create arrays:

function addProps(obj, arr, val) {

  if (typeof arr == 'string')
    arr = arr.split(".");

  var tmpObj, isArray = /^(.*)\[(\d+)\]$/.exec(arr[0])
  if (isArray && !Number.isNaN(isArray[2])) {
    obj[isArray[1]] = obj[isArray[1]] || [];
    obj[isArray[1]][isArray[2]] = obj[isArray[1]][isArray[2]] || {}
    tmpObj = obj[isArray[1]][isArray[2]];
  } else {
    obj[arr[0]] = obj[arr[0]] || {};
    tmpObj = obj[arr[0]];
  }

  if (arr.length > 1) {
    arr.shift();
    addProps(tmpObj, arr, val);
  } else
    obj[arr[0]] = val;

  return obj;

}


var myObj = {}
addProps(myObj, 'sub1[0].sub2.propA', 1)
addProps(myObj, 'sub1[1].sub2.propA', 2)

console.log(myObj)

I think that is possible to allow use "sub1[].sub2..." to just push into the sub1 array, instead of specify the index, but that's enough for me now.

Alynva
  • 499
  • 7
  • 10
0

Option 1:

Use Object.assign:

var test = {};
Object.assign(test, { hello: { world: "hello world" }});

console.log(test.hello.world); // "hello world"

Option 2:

Use Destructuring assignment:

var test = {};
test = {...test, ...{ hello: { world: "hello world" }}};

console.log(test.hello.world); // "hello world"
catwith
  • 875
  • 10
  • 13