1

I'm trying to convert an array of objects like this:

[{grandParentField:'grandParent1', parentField:'parent1', childField: 'child1'},
 {grandParentField:'grandParent1', parentField:'parent1', childField: 'child2'},
 {grandParentField:'grandParent2', parentField:'parent1', childField: 'child3'},
 {grandParentField:'grandParent2', parentField:'parent2', childField: 'child4'}]

into this form:

[
{
  text: 'grandparent1',
  items: [
    {
      text: 'parent1',
      items: [{ text: 'child1' }, { text: 'child2' }]
    }
  ]
},
{
  text: 'grandparent2',
  items: [
    {
      text: 'parent1',
      items: [{ text: 'child3' }]
    },
    {
      text: 'parent2',
      items: [{ text: 'child4' }]
    }
  ]
}

]

This Thread is similar to what I want, but not quite.

children will always be unique, but parents can have multiple grandparents.

Honestly I've tried so many things I'm not even sure which one to include as an example of what has gotten me closest.

Something like this but able to take in an array of Objects, and pump out the {text: string, items:[{text: string, items:[{text:string]]} structure:

var groupBy = function(xs, key) {
  return xs.reduce(function(rv, x) {
    (rv[x[key]] = rv[x[key]] || []).push(x);
    return rv;
  }, {});
};

console.log(groupBy(['one', 'two', 'three'], 'length'));

// => {3: ["one", "two"], 5: ["three"]}

2 Answers2

0

Without getting too crazy with types, I'd say that you want your output to be of this shape:

interface Tree {
    text: string,
    items?: Tree[]
}

So let's make a function called group() which takes your array and a list of keys that you want to process in the order they should be processed. So for your example it would be used like this:

const data = [
    { grandParentField: 'grandParent1', parentField: 'parent1', childField: 'child1' },
    { grandParentField: 'grandParent1', parentField: 'parent1', childField: 'child2' },
    { grandParentField: 'grandParent2', parentField: 'parent1', childField: 'child3' },
    { grandParentField: 'grandParent2', parentField: 'parent2', childField: 'child4' }
];
    
const groupedData = group(data, "grandParentField", "parentField", "childField");

Here's the implementation of group():

function group(data: Array<Record<string, string>>, key: string, ...otherKeys: string[]): Tree[] {
    const objMap: Record<string, any[]> = {}
    for (const d of data) {
        if (!(d[key] in objMap)) {
            objMap[d[key]] = []
        }
        objMap[d[key]].push(d);
    }
    return Object.keys(objMap).map(k => otherKeys.length ?
        {
            text: k,
            items: group(objMap[k], otherKeys[0], ...otherKeys.slice(1))
        } : {
            text: k
        }
    );

}

First we group the elements from data into a dictionary of arrays called objMap, where each element d goes into the key of objMap at d[key] (so the first element goes into the key named "grandParent1" if key is "grandParentField").

Once this grouping is done, we return a new array by walking through objMap's keys. If we have no otherKeys, we just return an array of {text: string} elements using the keys of objMap as the text field. If we do have other keys, then we need to recursively call group() on the elements stored in objMap at the proper key.

You can verify that this works for your example:

console.log(JSON.stringify(groupedData, undefined, 2));
/* [
  {
    "text": "grandParent1",
    "items": [
      {
        "text": "parent1",
        "items": [
          {
            "text": "child1"
          },
          {
            "text": "child2"
          }
        ]
      }
    ]
  },
  {
    "text": "grandParent2",
    "items": [
      {
        "text": "parent1",
        "items": [
          {
            "text": "child3"
          }
        ]
      },
      {
        "text": "parent2",
        "items": [
          {
            "text": "child4"
          }
        ]
      }
    ]
  }
] */

Playground link to code

jcalz
  • 125,133
  • 11
  • 145
  • 170
0

Recursive approach, should work for every n-nested input that you will provide:

const input =[{grandParentField:"grandParent1",parentField:"parent1",childField:"child1"},{grandParentField:"grandParent1",parentField:"parent1",childField:"child2"},{grandParentField:"grandParent2",parentField:"parent1",childField:"child3"},{grandParentField:"grandParent2",parentField:"parent2",childField:"child4"}];
 
const nestedGroupBy = (nodes, order, orderIdx = 0) => {
  const key = order[orderIdx]
  let grouped = nodes.reduce((acc, e, i) => {
    let node = acc.find(x => x.text == e[key])
    if (!node) {
      node = { text: e[key], items: [] }
      acc.push(node)
    }
    node.items ? node.items.push(e) : node.items = [e]
    return acc
  }, [])
  if (order[orderIdx + 1])
    grouped = grouped.map(e => ({
      text: e.text,
      items: nestedGroupBy(e.items, order, orderIdx + 1)
    }))
  else
    grouped = grouped.map(e => ({ text: e.text }) )
  return grouped
}
 
const res = nestedGroupBy(input, Object.keys(input[0]))

console.log(res)
.as-console-wrapper { max-height: 100% !important; top: 0; }
ulou
  • 3,894
  • 2
  • 28
  • 41