Home > OS >  generic function to create an array of nested objects from a single object of arrays
generic function to create an array of nested objects from a single object of arrays

Time:04-23

This is a very specific problem but I have an object with arrays:

let rawData = {
    one: ["a", "a", "a", "b", "b", "b", "c", "c", "c"],
    two: [1,2,3,1,2,3,1,2,3],
    three: [1,4,9,3,9,7,6,5,5]
  }

and I want to write a generic function that will create a set of nested objects based on the first array (here a, but I'd like to make this function not dependent on a specific key.

In my first try I wrote a function that parses this into an array of objects:

dataframe_for_plot = (data) => {
  var length
  var names = []
  for (var name in data) {
        if (data.hasOwnProperty(name))
            names.push(name);
        length = rawData[name].length;
  }

    var results = [];
    var item;
    for (var row = 0; row < length; row  ) {
        item = {};
        for (var col = 0; col < names.length; col  ) {
            item[names[col]] = data[names[col]][row];
        }
        results.push(item);
    }
    return results;
}

Which yeilds

dataframe_for_plot(rawData)
[
  {one: "a", two: 1, three: 1},
  {one: "a", two: 2, three: 4},
  {one: "a", two: 3, three: 9},
  {one: "b", two: 1, three: 3},
  {one: "b", two: 2, three: 9},
  {one: "b", two: 3, three: 7},
  {one: "c", two: 1, three: 6},
  {one: "c", two: 2, three: 5},
  {one: "c", two: 3, three: 5},
]

But I want to build on this function to get my desired output where the first key is used to create objects based on the unique number of that key (3 in this case), and a new value key is created to contain a nested array of the other keys (two and three in this case, grouped together by one)

let desired_output = [
  {
    one:"a",
    values: [
      {two:1, three:1},
      {two:2, three:4},
      {two:3, three:9}
    ],
  },
    {
    one:"b",
    values: [
      {two:1, three:3},
      {two:2, three:9},
      {two:3, three:7}
    ],
  },
    {
    one:"c",
    values: [
      {two:1, three:6},
      {two:2, three:5},
      {two:3, three:5}
    ],
  }
]

I think my function is a good start, but I need a little help working on the nested part 2! Thanks!

CodePudding user response:

Convert the object to an array of entries, and get the 1st item's key and vals. Reduce the vals to a Map. The Map would have a single object for each unique value from the 1st array if values (one in your case). Then iterate the array of the other values, take the respective value from each [key, arr] pair, and convert them to an object using Object.fromEntries().

Convert the Map values iterator back to an array using Array.from().

const fn = obj => {
  const [[key, vals], ...values] = Object.entries(obj)
  
  return Array.from(vals.reduce((acc, v, i) => {
    if(!acc.has(v)) acc.set(v, { [key]: v, values: [] })
    
    acc.get(v).values.push(Object.fromEntries(
      values.map(([k, arr]) => [k, arr[i]])
    ))
    
    return acc
  }, new Map()).values())
}

const rawData = {
  one: ["a", "a", "a", "b", "b", "b", "c", "c", "c"],
  two: [1,2,3,1,2,3,1,2,3],
  three: [1,4,9,3,9,7,6,5,5]
}

const result = fn(rawData)

console.log(result)

If you want to control the order of properties, you can pass an order array, and then create the entries manually in the order you've stated:

const fn = (order, obj) => {
  const [[key, vals], ...values] = order.map(key => [key, obj[key]])
  
  return Array.from(vals.reduce((acc, v, i) => {
    if(!acc.has(v)) acc.set(v, { [key]: v, values: [] })
    
    acc.get(v).values.push(Object.fromEntries(
      values.map(([k, arr]) => [k, arr[i]])
    ))
    
    return acc
  }, new Map()).values())
}

const rawData = {
  one: ["a", "a", "a", "b", "b", "b", "c", "c", "c"],
  two: [1,2,3,1,2,3,1,2,3],
  three: [1,4,9,3,9,7,6,5,5]
}

const result = fn(['one', 'three', 'two'], rawData)

console.log(result)

  • Related