2

We're trying to set nested object values based on dot notation strings.

Example input:

{
    "bowtime": [
       "30",
       " 1",
       " 3",
       " 20"
    ],
    "bowstate.levi.leviFlo.totalFloQuot": ".95",
    "bowstate.crem.cremQuot": "79" 
}

Desired output:

{
    "bowstate": {
       "levi": {
           "leviFlo": {
               "totalFloQuot": 0.95
           }
       },
       "crem": {
           "cremQuot": 79
       }
    },
    "bowtime": [
       "30",
       " 1",
       " 3",
       " 20"
    ],
}

So far the code works fine, but it seems overly complex, and only allows for 4 layers of nesting. How can we simplify this code, and get it working for references with more than 4 layers of nesting:

const dayspace = {};
var keyArr = Object.keys(input);

for (key in keyArr) {
  if ( keyArr[key].indexOf('.') > -1 ) {
    var setArr = keyArr[key].split('.');
    dayspace[setArr[0]] = dayspace[setArr[0]] || {}
    for (var s = 0; s < setArr.length; s++) {
      if (s == 1) {
        if (setArr.length > s + 1) dayspace[setArr[0]][setArr[s]] = {}
        else dayspace[setArr[0]][setArr[s]] = req.body[keyArr[key]]
      }
      if (s == 2) {
        if (setArr.length > s + 1) dayspace[setArr[0]][setArr[1]][setArr[s]] = {}
        else dayspace[setArr[0]][setArr[1]][setArr[s]] = req.body[keyArr[key]]
      }
      if (s == 3) {
        if (setArr.length > s + 1) dayspace[setArr[0]][setArr[1]][setArr[2]][setArr[s]] = {}
        else dayspace[setArr[0]][setArr[1]][setArr[2]][setArr[s]] = req.body[keyArr[key]]
      }
      if (s == 4) dayspace[setArr[0]][setArr[1]][setArr[2]][setArr[3]][setArr[s]] = req.body[keyArr[key]]
    }
  }
  else {
    dayspace[keyArr[key]] = req.body[keyArr[key]]
  }
}

4 Answers 4

6

I'd split the key by . and use reduce to create all but the last nested value, if needed, and then assign the value to the last object created or found in the reduce callback:

const input = {
    "bowtime": [
       "30",
       " 1",
       " 3",
       " 20"
    ],
    "bowstate.levi.leviFlo.totalFloQuot": ".95",
    "bowstate.crem.cremQuot": "79" 
};

const output = Object.entries(input).reduce((outerObj, [key, val]) => {
  if (!key.includes('.')) {
    outerObj[key] = val;
    return outerObj;
  }
  const keys = key.split('.');
  const lastKey = keys.pop();
  const lastObj = keys.reduce((a, key) => {
    // Create an object at this key if it doesn't exist yet:
    if (!a[key]) {
      a[key] = {};
    }
    return a[key];
  }, outerObj);
  // We now have a reference to the last object created (or the one that already existed
  // so, just assign the value:
  lastObj[lastKey] = val;
  return outerObj;
}, {});
console.log(output);

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

Comments

1

I have done similar things in my project. I have achieved it with a popular package called Flat. Link: https://github.com/hughsk/flat

var unflatten = require('flat').unflatten

unflatten({
    'three.levels.deep': 42,
    'three.levels': {
        nested: true
    }
})

// {
//     three: {
//         levels: {
//             deep: 42,
//             nested: true
//         }
//     }
// }

This package can make your nested structure flat and flatten structure nested as well. There are other useful methods there also. So it will be more flexible.

I think you should use it which will lead to less bugs in your project.

Comments

0

You could use Object.entires to get an array of key-value pairs within your object and then .reduce() your object keys by using .split(".") to get the single object properties into an array which you can then use to build your new object:

const obj = {
  "bowtime": [
    "30",
    " 1",
    " 3",
    " 20"
  ],
  "bowstate.levi.leviFlo.totalFloQuot": ".95",
  "bowstate.crem.cremQuot": "79"
};

const res = Object.entries(obj).reduce((acc, [k, v]) => {
  const keys = k.split('.');
  let cur = acc;
  keys.length > 1 && keys.forEach(ka => {
    cur[ka] = cur[ka] || {};
    cur = cur[ka];
  });
  cur[keys.pop()] = v;
  return acc;
}, {});

console.log(res);

Comments

0

You could use a shorter approach by using a function for the splitted path to the value and generate new objects for it.

function setValue(object, path, value) {
    var last = path.pop();
    path.reduce((o, k) => o[k] = o[k] || {}, object)[last] = value;
}

var object = { bowtime: ["30", " 1", " 3", " 20" ], "bowstate.levi.leviFlo.totalFloQuot": ".95", "bowstate.crem.cremQuot": "79" };

Object.entries(object).forEach(([key, value]) => {
    if (!key.includes('.')) return;
    setValue(object, key.split('.'), value);
    delete object[key];
});

console.log(object);
.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.