Home > database >  How to create a nested data-structure where a value is supposed to become an array type in the prese
How to create a nested data-structure where a value is supposed to become an array type in the prese

Time:11-03

I have input data like this:

[{
  "name": "outField2",
  "value": "something"
}, {
  "name": "outField3[index].outField4",
  "value": "something"
}, {
  "name": "outField3[index].outField5",
  "value": "something"
}, {
  "name": "outField3[index].outField6.outField7",
  "value": "something"
}]

I am trying to achieve an output like this based on substring '[index]' (i.e. if that substring is not present then that element should be an object instead of an array):

{
  "outField2": "something",
  "outField3[index]": [{
    "outField4": "something",
    "outField5": "something",
    "outField6": {
      "outField7": "something"
    }
  }]
}

My current code (below) is able to produce the outField3 as an object if there is no substring '[index]' but I'm unable to find a good solution to generate it as an array in the presence of the substring. Can someone help out? I've tried a few options but none gives me the desired result.

function doThis(item, index) {
  let path = map[index].name.split(".");
  if (path.length > 1) {
    createNestedObject(mapOutput, path, map[index].value);
  } else {
    mapOutput[map[index].name] = map[index].value;
  };
};

function createNestedObject(element, path, value) {
  var lastElement = arguments.length === 3 ? path.pop() : false;

  for (var i = 0; i < path.length; i  ) {
    if (path[i].includes('[index]')) {
      /*some logic here to push the child elements
      that do not contain [index] as an array into
      the ones that contain [index]*/
    } else {
      element = element[path[i]] = element[path[i]] || {};
    };
  }
  if (lastElement) element = element[lastElement] = value;

  return element;
};

const map = [{
  "name": "outField2",
  "value": "something"
}, {
  "name": "outField3[index].outField4",
  "value": "something"
}, {
  "name": "outField3[index].outField5",
  "value": "something"
}, {
  "name": "outField3[index].outField6.outField7",
  "value": "something"
}];
let mapOutput = {};

map.forEach(doThis);

let mapOutputJSON = JSON.stringify(mapOutput, null, 2);

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

CodePudding user response:

you can do something like this

const data = [{
    "name": "outField2",
    "value": "something"
  },
  {
    "name": "outField3[index].outField4",
    "value": "something"
  },
  {
    "name": "outField3[index].outField5",
    "value": "something"
  },
  {
    "name": "outField3[index].outField6.outField7",
    "value": "something"
  }
]

const buildObject = (paths, value, obj) => {
  if (paths.length === 0) {
    return value
  }
  const [path, ...rest] = paths
  if(path.includes('[index]')) {
    
    return {
    ...obj,
    [path]: [buildObject(rest, value, (obj[path] || [])[0] || {})]
  }
  }
  return {
    ...obj,
    [path]: buildObject(rest, value, obj[path] || {})
  }
}

const result = data.reduce((res, {
    name,
    value
  }) => buildObject(name.split('.'), value, res), {})

console.log(result)

CodePudding user response:

A possible generic approach which in my opinion also assigns the correct type of the OP's "outField3[index]" property (object type instead of an Array instance) is based on reduce where ...

  • the outer loop iterates the array of { name, value } items
  • by executing a single function accumulateObjectTypeFromPathAndValue where ...
    • this function does split each name-value into an array of object-path keys which then gets iterated by the inner reduce method where the passed object programmatically accumulates nested key-value pairs.

function accumulateObjectTypeFromPathAndValue(root, path, value) {
  path
    .split('.')
    .reduce((obj, key, idx, arr) => {

      if (!obj.hasOwnProperty(key)) {
        Object.assign(obj, {
          [ key ]: (idx === arr.length - 1)
            ? value
            : {},
        });
      }
      return obj[key];

    }, root);

  return root;
}
console.log(
  [{
    "name": "outField2",
    "value": "something"
  }, {
    "name": "outField3[index].outField4",
    "value": "something"
  }, {
    "name": "outField3[index].outField5",
    "value": "something"
  }, {
    "name": "outField3[index].outField6.outField7",
    "value": "something"

  }].reduce((result, { name: path, value }) => {
    return accumulateObjectTypeFromPathAndValue(result, path, value);
  }, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

The above implementation of the 2nd reducer function then could be changed according to the OP's custom array-type requirements ...

function accumulateCustomObjectTypeFromPathAndValue(root, path, value) {
  path
    .split('.')
    .reduce((obj, key, idx, arr) => {

      if (!obj.hasOwnProperty(key)) {
        Object.assign(obj, {
          [ key ]: (idx === arr.length - 1)
            ? value
            : {},
        });
        if (key.endsWith('[index]')) {
          obj[ key ] = [obj[ key ]];
        }
      }
      return Array.isArray(obj[ key ])
      //? obj[ key ].at(-1)                 // last item.
        ? obj[ key ][obj[ key ].length - 1] // last item.
        : obj[ key ];

    }, root);

  return root;
}
console.log(
  [{
    "name": "outField2",
    "value": "something"
  }, {
    "name": "outField3[index].outField4",
    "value": "something"
  }, {
    "name": "outField3[index].outField5",
    "value": "something"
  }, {
    "name": "outField3[index].outField6.outField7",
    "value": "something"

  }].reduce((result, { name: path, value }) => {
    return accumulateCustomObjectTypeFromPathAndValue(result, path, value);
  }, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

  • Related