3

I have an object called MyObject and a method called createEntry() which aims to create a new property for MyObject and sub-properties for the property or just skip it altogether if it already exists.

My code:

var MyObject = {
    createEntry: function (val1, val2, val3, val4) {
        this[val1] = this[val1] || {};
        this[val1][val2] = this[val1][val2] || {};
        this[val1][val2][val3] = val4;
    }
};

MyObject.createEntry("val1", "val2", "val3", "val4");

As shown in the above function, I'm trying to create a new sub-object for each argument of the method createEntry() except for the last two, where val3 is a property or method and val4 is its value.

With my method in its current state, I can only reach up to level 3 with its subsequent ones requiring longer and longer code. I assume the above can be achieved with a while loop, but I haven't been able to figure it out yet.

Question: How can I create unlimited sub-objects based on the number of arguments passed on the above function in a tree fashion that looks like the following:

var MyObject = {
   val1: {
      val2 {
         val3: val4
      }
   }
}
Angel Politis
  • 10,955
  • 14
  • 48
  • 66

3 Answers3

4

reduceRight seems perfect for this:

function createEntry(...args) {
  return args.reduceRight(function(prev, curr) {
    return {[curr]: prev};
  });
}
console.log(createEntry("val1", "val2", "val3", "val4"));
Oriol
  • 274,082
  • 63
  • 437
  • 513
1

Well, when creating unlimited sub-objects using parameters, I'd iterate each parameter, having the last object I entered as reference. This code is enough to understand.

Note: Object.assign is a new browser implementation, but it has polyfills already. I use this method to concatenate object with object when a entry name already exists in a object

Object['prototype']['createEntries'] = function() {
    /* store last object location */
    var lastObj = this

    /**
     * Note: each argument is a object that specify the following properties -->
     * inner : the entry object
     * *name : the entry property name
     */

    for (var i = 0, arg, existent, len = arguments.length; i < len; ++i) {
        if (typeof (arg = arguments[i]) === 'object' && (typeof arg.inner === 'object' ? true : arg.inner = {} ))
            /* create new entry/keep existent entry and reserve it in lastObj */
            lastObj = (typeof (existent = lastObj[arg.name]) === 'object' ?
                    lastObj[arg.name] = Object.assign(existent, arg.inner) :
                    lastObj[arg.name] = arg.inner)
    }

    return this
}

Then this is a basic usage

({}).createEntries({
    name: "val1"
}, {
    name: "val2"
})
/* {
       val1: {
            val2: {
            }
       }
   }
*/

I hope this is what you want

  • Hey @TheProHands, thanks for the answer. The names 'Object', 'val1', 'val2' etc are used in this example in an attempt to create a minimal piece of code, so don't worry about conflicts in my code. Now coming to your answer, correct me if I'm wrong, but I don't think this is the way to go. Each sub-object will have a specified name and not some abstract 'val123' etc and also I don't see how the last two arguments are in that format: **`val3`: val4**. Thanks anyway. – Angel Politis Sep 15 '16 at 23:23
  • @AngelPolitis Ah! You need to specify this in parameters. I'll update it –  Sep 15 '16 at 23:24
  • I corrected a typo inside the code. If you prefer the normal style of code, I can update it to make more legible. Yea, I tested it. Have you checked it again? –  Sep 15 '16 at 23:36
  • 1
    I'm checking it out now... Ok, one thing that's bugging me about trying to understand your code is why you attach the **`createEntry()`** method to the native Object and not **`MyObject`**... – Angel Politis Sep 15 '16 at 23:41
  • Also, why is **`createEntry()`** taking an object as an argument. You have heavily modified the initial code. Thanks for the effort! – Angel Politis Sep 15 '16 at 23:47
  • @AngelPolitis You can still implement the same function inside `createEntry` function since `this` refers to your **`Object`** –  Sep 15 '16 at 23:49
  • 2
    Polluting `Object.prototype` is dangerous. – Oriol Sep 15 '16 at 23:54
  • @Oriol Yes, when making libraries. –  Sep 15 '16 at 23:55
0

Oriol gave a fantastic answer, but his function, due to the way it's built, is not really useful as it returns a new object instead of modifying MyObject.

So, in order to actually modify MyObject, instead of the first return, we have to do something else that will modify MyObject without overwriting it.

To do just that, a function that expands the MyObject must be built:

expand: function (object) {
   for (var key in object) if (!!object[key]) {
      if (key in this) throw new Error("`" + key + "` already exists.");
      else this[key] = object[key];
   }
}

Then, all we have to do is to use @Oriol's answer without the first return statement as an argument to expand():

createEntry: function () {
   this.expand([].reduceRight.call(arguments, function(previous, current) {
      return {[current]: previous};
   }));
}

Full code:

var MyObject = {
  createEntry: function() {
    this.expand([].reduceRight.call(arguments, function(previous, current) {
      return {
        [current]: previous
      };
    }));
  },
  expand: function(object) {
    for (var key in object)
      if (!!object[key]) {
        if (key in this) throw new Error("`" + key + "` already exists.");
        else this[key] = object[key];
      }
  }
};

MyObject.createEntry("val1", "val2", "val3", "val4");
console.log(MyObject);

Thanks to everyone for the help!

Community
  • 1
  • 1
Angel Politis
  • 10,955
  • 14
  • 48
  • 66