Home > Blockchain >  How to groupby a sub property inside a property?
How to groupby a sub property inside a property?

Time:12-09

I've got an object with multiple key holding an array of values. Inside that array, there is a nested value on which I want to group on, while maintaining the main key.

So in my data I've got:

{
  "foo1": [
    {
      "name": "foo1",
      "subpart": {
        "part": "bar1"
      }
    },
    {
      "name": "foo1",
      "subpart": {
        "part": "bar1"
      }
    },
    {
      "name": "foo1",
      "subpart": {
        "part": "bar2"
      }
    },
    {
      "name": "foo1",
      "subpart": {
        "part": "bar2"
      }
    },
    {
      "name": "foo1",
      "subpart": {
        "part": "bar3"
      }
    },
    {
      "name": "foo1",
      "subpart": {
        "part": "bar3"
      }
    }
  ],
  "foo2": [
    {
      "name": "foo2",
      "subpart": {
        "part": "bar1"
      }
    },
    {
      "name": "foo2",
      "subpart": {
        "part": "bar1"
      }
    },
    {
      "name": "foo2",
      "subpart": {
        "part": "bar2"
      }
    },
    {
      "name": "foo2",
      "subpart": {
        "part": "bar3"
      }
    },
    {
      "name": "foo2",
      "subpart": {
        "part": "bar3"
      }
    }
  ]
}

and I want it to become:

{
  "foo1": {
    "bar1": [
      {
        "name": "foo1",
        "subpart": {
          "part": "bar1"
        }
      },
      {
        "name": "foo1",
        "subpart": {
          "part": "bar1"
        }
      }
    ],
    "bar2": [
      {
        "name": "foo1",
        "subpart": {
          "part": "bar2"
        }
      },
      {
        "name": "foo1",
        "subpart": {
          "part": "bar2"
        }
      }
    ],
    "bar3": [
      {
        "name": "foo1",
        "subpart": {
          "part": "bar3"
        }
      },
      {
        "name": "foo1",
        "subpart": {
          "part": "bar3"
        }
      }
    ]
  },
  "foo2": {
    "bar1": [
      {
        "name": "foo2",
        "subpart": {
          "part": "bar1"
        }
      },
      {
        "name": "foo2",
        "subpart": {
          "part": "bar1"
        }
      }
    ],
    "bar2": [
      {
        "name": "foo2",
        "subpart": {
          "part": "bar2"
        }
      }
    ],
    "bar3": [
      {
        "name": "foo2",
        "subpart": {
          "part": "bar3"
        }
      },
      {
        "name": "foo2",
        "subpart": {
          "part": "bar3"
        }
      }
    ]
  }
}

Right now I've found some examples, but that only does grouping on just one level, where they 'key' is the first group, but I've got a second property in some subvalue to be the second level of grouping (if that makes any sense).

Best I've got so far is:

function groupBy<T>(arr: T[], fn: (item: T) => any) {
  return arr.reduce<Record<string, T[]>>((prev, curr) => {
      const groupKey = fn(curr);
      const group = prev[groupKey] || [];
      group.push(curr);
      return { ...prev, [groupKey]: group };
  }, {});
}

and then:

const grouped = groupBy(data, (x) => x.subpart.part)

But that groups it into a main key which I don't want.

CodePudding user response:

Essentially you need to map groupBy over each value in data.

Mapping over objects is somewhat difficult/not straight-forward in TypeScript. What you can do is using Object.entries and Object.fromEntries to convert the object to an array first, map it and convert it back to an object. doing this, however, breaks type inference and makes the result of type any. So be careful when you need to do this while maintaining type-safety.

You might also want to consider refactoring your code such that an array-based structure is used as input and/or output for this transformation, as its easier to work with, and easier to maintain type-safety with.

const data = { "foo1": [...], ...}

const dataArr = Object.entries(data);
const mappedArr = dataArr.map(([k,v]) => [k, groupBy(v, (x) => x.subpart.part)]);
const result = Object.fromEntries(mappedArr);

TS Playground

  • Related