0

So I was working with a colleague who showed me how i could solve a particular problem: how to get a flat object into a nested object. The object properties are named in such a way that they can be sliced into their relevant key named and then nested. His solution works beautifully, but when I ran through his code myself later I couldn't understand how it works.

I'm essentially taking a excel worksheet and creating json from it but for argument sake i'll remove the excel parts and just add the example structure which comes out of the excel parser:

//Example data
var result = {
    frame1.title: "heading",
    frame1.division: "first",
    frame1.data[0].month: "Jan",
    frame1.data[0].value: "54",
}


function deepInsert (o, path, value) {
let next, cur = o
path.forEach((p,i) => {
    next = cur[p]
    if (i == path.length -1) {
        cur[p] = value
    } else {
        if (!next) {
            next = cur[p] = {}
        }
        cur = next
    }
})
}
function processWorkbook () {

const result = json.map(item => {
    var o = {foo: "bar"}
    Object.keys(item).forEach(prop => {
        deepInsert(o, prop.split('.'), item[prop])
        console.log(JSON.stringify(o, 0, '  '));

    })
    return o
})

console.log(JSON.stringify(result, 0, '  '))
}

From what I can tell it looks like hes passing in 'o', which is a blank object, then the loop in the deepInsert function is assigning data to not the param, but the object in the calling function, because everytime through the loop, my console log shows more being added to the object.

I also don't understand this part: next = cur[p] = {}. For some reason a triple assignment throws me an error in the chrome repl but not in that function? Im just so confused, any help would be great!

Abhishek
  • 1,477
  • 1
  • 10
  • 18
C02Equinox
  • 61
  • 1
  • 6

1 Answers1

1

The function deepInsert recives the following params:

  • An Object (it will be modified)
  • The array of path for the value( 'foo.bar.x' needs to become ['foo','bar', 'x'] )
  • The value to be inserted

and does this:

  1. Iterates on the path Array
  2. if the current path iteration isn't the last, it will Initialize a
    new Object on it.
  3. if the current path IS the last one, the passed value is set to it.

The function processWorkbook just iterates on the object keys to send the parameters to the deepInsert function. This could be done directly on the deepInsert.

And that's it. The problem is the function has unused variables and complicated code. A more simple and documented function:

function unnestObject(obj = {}) {

    let newObject = {}, //The object to return

        current; // the object position holder

    for (var key in obj) { // iterate on recived object keys

        current = newObject // Makes the current object the new Object root

        let path = key.split('.') // Split the current key

        path.forEach((p, i) => { // iterate on the key paths

            if ((path.length - 1) !== i) //if the path isn't the last one

                current[p] = current[p] || {} // initialize a new object on that path (if a object was previously initialized, it is preserved)

            else //if the path is the last one

                current[p] = obj[key] // sets the value of the initial object key

            current = current[p] // Updates the current to the next node

        })
    }
    return newObject; //returns the new object
}

//Example data [DOESNT WORK WITH ARRAYS]
var data = {
    "frame1.title": "heading",
    "frame1.division": "first",
    "frame1.data.month": "Jan",
    "frame1.data.value": "54",
}

console.log(unnestObject(data))
// Prints
// {
//   "frame1": {
//     "title": "heading",
//     "division": "first",
//     "data": {
//       "month": "Jan",
//       "value": "54"
//     }
//   }
// }

Note: Both functions doesn't support arrays, if you pass something like {"foo.bar[0].value": 42}, foo.bar will be a object. You can detect the array [] keys and make it initialize an array instead of an object on the iteration


About the next = cur[p] = {}, you can assign one value to multiple variables at once. you can do foo = bar = 42, both will have 42. You can also do foo = bar = {}. both will have pointers to the same object, if you change a value on one, another will already have the change.

This is very userful for get and initialize global values for instance

var foo = window.foo = window.foo || {bar: 42};

This line will make foo and window.foo recive the object on window.foo . if window.foo wasn't initialized yet, it will recive the new object.

Maxwell s.c
  • 1,583
  • 15
  • 29
  • Ok your answer makes a lot more sense to me. But theres a few things i'm still hazy on. You're only defining that `current = newObject` once, but `newObject` is staying updated with all the stuff you're adding each loop. I was under the impression that variable assignment was a one way thing. AKA `foo = bar` would copy bar to foo, but then the two variables could diverge values. And if this isn't the case, and the two values are being updated at the same time, why is it that `newObject` has the full object value, but `current` only has the new root value each time? – C02Equinox Mar 04 '17 at 23:29
  • Ok so reading this article cleared up some of that ambiguity for me (http://stackoverflow.com/questions/509579/how-does-variable-assignment-work-in-javascript) but I still don't understand, if you're updating current to be a new object, then why does `newObject` get updated since these two variables should now reference different objects? – C02Equinox Mar 04 '17 at 23:40