1

I was wondering if it is possible to dynamically generate an object with an array of strings in dot notation. I would like to dynamically build a JSON object from a CSV file. The goal is to build the CSV as JSON, then filter the properties and make a new JSON object.

So I would like to pass in something like this..

var obj = {};
var keyArray = ['meta', 'logos', 'warranty', 'specs', 'specs.engine', 'specs.engine.hp', 'specs.engine.rpm', 'specs.engine.manufacturer'];

The end result would be something like this...

obj = {
 meta: {
 },
 logos: {
 },
 specs: {
    engine: {
       hp: {
       }
    }
 }
}

Here is the main function

function addObjectsByKey(obj, keyArray) {

    for (var key in keyArray) {

        // If keyword is not in object notation
        if (!(keyArray[key].match(/\./))) {

            // If the object property is not set, set it
            if (!(obj[keyArray[key]])) {

                obj[keyArray[key]] = {};

            }

        } else {

            // Split array element (in dot notation) into an array of strings
           // These strings will be object properties
            var pathAsArray = keyArray[key].split('.');
            var path = null;

            for (var k in pathAsArray) {
                if (path == null) {
                    obj[pathAsArray[k]] = {};
                    path = pathAsArray[k];
                } else {
                    obj[path][pathAsArray[k]] = {};
                    path += '.' + pathAsArray[k];
                }
            }
            // throw Error('end');

        }

    }
    // return obj;
}
4
  • The problem you are going to have here is you don't know the type of your leaf nodes. Consider your first path "meta". In your output you show that this created and empty object. How do you know it should be an object and not an array. For that matter how do you know it should not be a primitive. Commented Apr 4, 2018 at 17:54
  • Should 'specs.engine.hp' really be an object. I imagine it should be a primitive number. Commented Apr 4, 2018 at 17:55
  • I feel kind of sorry for the OP here. Both answers have gone with a functional approach that is essentially illegible. Some time in the last few years programming stoped being about communicating ideas and just became cram it all on a single line. Commented Apr 4, 2018 at 18:05
  • @bhspencer "Should 'specs.engine.hp' really be an object" for the sake of example I shortened some things up. It's called something else in my actual data and has properties setting hp and rpm. Commented Apr 4, 2018 at 18:35

2 Answers 2

2

You can use forEach loop and inside you can split each element on . and then use reduce method to build nested object.

var keyArray = ['meta', 'logos', 'warranty', 'specs', 'specs.engine', 'specs.engine.hp', 'specs.engine.rpm', 'specs.engine.manufacturer'];
const result = {}
keyArray.forEach(key => {
  // Loop array of keys
  // Split each key with . and use reduce on that 
  // In each iteration of reduce return r[e] which is going to be value if property exists
  // or new object if it doesn't
  // This way you can go to any object depth as long as keys match existing keys in object.
  key.split('.').reduce((r, e) => r[e] = (r[e] || {}), result)
})

console.log(result)

Here is another approach using for loops that will return the same result.

var keyArray = ['meta', 'logos', 'warranty', 'specs', 'specs.engine', 'specs.engine.hp', 'specs.engine.rpm', 'specs.engine.manufacturer' ];
const result = {}

for(var i = 0; i < keyArray.length; i++) {
  const keys = keyArray[i].split('.');
  let ref = result;
  for(var j = 0; j < keys.length; j++) {
    const key = keys[j];
    if(!ref[key]) ref[key] = {}
    ref = ref[key]
  }
}

console.log(result)

Sign up to request clarification or add additional context in comments.

1 Comment

Any chance you explain how the code works. Its cool that you have used lambda and functional programming idioms but its is completely unintelligible to me.
1

You can use the function reduce along with a nested forEach to build the path.

  • The first reduce will accumulate the nested operation.
  • The nested forEach will build the object and its children according to the current path separated by dots.

let keyArray = ['meta', 'logos', 'warranty', 'specs', 'specs.engine', 'specs.engine.hp', 'specs.engine.rpm', 'specs.engine.manufacturer'],
    newObj = keyArray.reduce((accum, path) => {
      let previous = accum;
      path.split('.').forEach(key => {
        if (previous[key]) previous = previous[key];
        else previous = previous[key] = {};
      });
      
      return accum;
    }, {});

console.log(newObj);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.