1

I have been using a version of the function below to create and/or add values to nested objects:

    function assign(obj, keyPath, value) {
      const lastKeyIndex = keyPath.length-1;
       for (let i = 0; i < lastKeyIndex; ++ i) {
         const key = keyPath[i];
         if (!(key in obj)){
           obj[key] = {}
         }
         obj = obj[key];
       }
       obj[keyPath[lastKeyIndex]] = value;
    }

(Posted in 2011 by kennytm, slightly modified above: Javascript: how to dynamically create nested objects using object names given by an array).

Example usage to specify a value in which the keys represent the (0) database name, (1) table name, (3) id value, and (4) column name:

    let obj = {}
    assign(obj, ['farm', 'products', '25', 'product_name'], 'lettuce');

    console.log(JSON.stringify(obj));
    /* (reformatted)
    {
      "farm": {
        "products": {
          "25": {
            "product_name":"lettuce"
          }
        }
      }
    } 
    */

We can add a second value for the same row:

    assign(obj, ['farm', 'products', '25', 'product_unit'], 'head');

    console.log(JSON.stringify(obj));
    /* (reformatted)
    {
      "farm": {
        "products": {
          "25": { 
            "product_name":"lettuce",
            "product_unit":"head"
          }
        }
      }
    }
    */

Or additional values from different rows, tables, and databases:

    assign(obj, ['farm', 'equipment', '17', 'equipment_name'], 'tractor');

    console.log(JSON.stringify(obj));
    /* (reformatted)
    {
      "farm": {
        "products": {
          "25": {
            "product_name": "lettuce",
            "product_unit": "head"
          }
        },
        "equipment": {
          "17": {
            "equipment_name": "tractor"
          }
        }
      }
    }
    */

The function works perfectly but I can't figure out how it manages to aggregate the key path. It would appear to simply create or replace an existing object with an object consisting of only the last key and the value. In fact, if I execute the same statements not inside a function and without using a loop, the statements do exactly that.

(Starting with assignment of the first value to an empty object):

    let obj = {}
    let key;
    
    // first iteration of the function's loop
    key = 'farm';
    if (!(key in obj)) {
      obj[key] = {}
    }
    obj = obj[key];
    
    // second iteration
    key = 'products';
    if (!(key in obj)) {
      obj[key] = {}
    }
    obj = obj[key];
    
    // third iteration
    key = '25';
    if (!(key in obj)) {
      obj[key] = {}
    }
    obj = obj[key];
    
    // final line from the function
    obj['product name'] = 'lettuce';

    console.log(JSON.stringify(obj));
    // {"product name":"lettuce"}

As you can see, the object is not nested but simply replaced at each step.

What magic makes the function work differently?

Dale Thompson
  • 115
  • 1
  • 7
  • It might help to start with `const myObj = {}; assign(myObj, ['farm', 'products', '25', 'product_name'], 'lettuce'); console.log(myObj)` - not named the same as the function parameter `obj`. – Bergi Aug 19 '21 at 00:34

1 Answers1

2

The difference is that you didn't save a variable with a reference to the original object. When you reassign obj, you no longer have a variable containing the parent.

When using the function, that variable is in the function caller. Reassigning the local variable obj has no effect on the caller's variable.

To emulate this in your manual steps, change the beginning to:

const original = {};
let obj = original;

This is analogous to the way the function parameter is passed.

Then at the very end, do

console.log(JSON.stringify(original));

and you should see the whole object with all the nested properties added.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • Thank you, Barmar and Bergi! I knew the magic was related to the fact that an object parameter to a function becomes a copy of the global reference to the object. But I couldn't go any further. Your answers should help me better understand how to treat objects in functions. – Dale Thompson Aug 19 '21 at 01:08
  • I commend you for going to all that effort in the first place. Manually executing code is one of the best ways to understand how it works. We get so many people just posting code and asking "Please explain how this code works". – Barmar Aug 19 '21 at 01:11