Let's introduce a function called nestedGroups()
, which operates on an array of objects named arr
, and a sequence of keys such as key1
, key2
, key3
, etc., belonging to the elements in arr
. When calling
nestedGroups(arr, key1, key2, key3)
, it will result in an object where the key is the pluralized version (by appending "s") of
key1
, and the corresponding value contains groupings for subsequent keys, and so forth.
const ng = nestedGroups(
[{ a: 1, b: 2 }, { a: 1, b: 3 }, { a: 4, b: 5 }, { a: 4, b: 6 }],
"a", "b"
);
/* const ng: { as: { a: number; bs: { b: number; }[]; }[]; } */
console.log(ng)
/* {
"as": [
{"a": 1, "bs": [{"b": 2}, {"b": 3}]},
{"a": 4, "bs": [{"b": 5}, {"b": 6}]}
]
} */
An implementation of nestedGroups()
is shown below:
// implementation
function nestedGroups(arr: Record<string, any>[], ...keys: string[]): any {
const [firstKey, ...restKeys] = keys;
if (firstKey === undefined) return {};
const retmap = new Map<any, any>();
arr.forEach(v => {
const val = v[firstKey];
if (!(retmap.has(val))) retmap.set(val, []);
retmap.get(val)!.push(v);
});
return {
[firstKey + "s"]: Array.from(retmap.entries()).map(([k, v]) =>
({ [firstKey]: k, ...nestedGroups(v, ...restKeys) }))
}
}
The function above recursively calls itself until all keys are processed, grouping values accordingly. To fully support TypeScript, we can define a type NestedGroups<T, K>
that represents the return value of nestedGroups(arr, ...keys)
:
type NestedGroups<T extends Record<K[number], Primitive>, K extends string[]> =
K extends [infer F, ...infer R] ? F extends string ? { [P in \`\${F}s\`]:
Array<Pick<T, F> & NestedGroups<T, Extract<R, string[]>> extends
infer O ? { [P in keyof O]: O[P] } : never>; } : never : {};
type Primitive = null | undefined | number | bigint | string | boolean | symbol;
Lastly, we declare the overloaded function signature for nestedGroups()
with strong typing:
// call signature
function nestedGroups<T extends Record<K[number], Primitive>, K extends string[]>
(arr: T[], ...keys: K): NestedGroups<T, K>;
A sample test using your input data can be seen below:
const r = nestedGroups(input, "PurchaseInvoice_id", "PurchaseInvoicePosition_id", "ProductStock_id");
const output = r.PurchaseInvoice_ids;
/* const output: {
PurchaseInvoice_id: string;
PurchaseInvoicePosition_ids: {
PurchaseInvoicePosition_id: string;
ProductStock_ids: {
ProductStock_id: string | null;
}[];
}[];
}[] */
console.log(output);
/*
[{
"PurchaseInvoice_id": "8e54a096-568b-48d9-8461-826be53a32da",
"PurchaseInvoicePosition_ids": [
{
"PurchaseInvoicePosition_id": "44edfd7f-bc9e-4155-ad5c-5dace9c7c31a",
"ProductStock_ids": [
{"ProductStock_id": "0a701dbc-2661-4d67-b764-632cfb67334f"},
{"ProductStock_id": "15278807-794a-4727-9bcb-f7f68dfb4d41"},
{"ProductStock_id": "0ac9fcd7-73f0-47b1-8fbc-3948863e7a89"}
]
},
// Additional data follows...
]
*/
This exemplifies how to leverage strong types in nestedGroups()
, ensuring correctness and offering detailed insights into the resulting structure.