I have an database result as shows in file:
So I have input data as follows:
const input = [
{
PurchaseInvoice_id: '8e54a096-568b-48d9-8461-826be53a32da',
PurchaseInvoicePosition_id: '44edfd7f-bc9e-4155-ad5c-5dace9c7c31a',
ProductStock_id: '0a701dbc-2661-4d67-b764-632cfb67334f',
},
{
PurchaseInvoice_id: '8e54a096-568b-48d9-8461-826be53a32da',
PurchaseInvoicePosition_id: '44edfd7f-bc9e-4155-ad5c-5dace9c7c31a',
ProductStock_id: '15278807-794a-4727-9bcb-f7f68dfb4d41',
},
{
PurchaseInvoice_id: '8e54a096-568b-48d9-8461-826be53a32da',
PurchaseInvoicePosition_id: '44edfd7f-bc9e-4155-ad5c-5dace9c7c31a',
ProductStock_id: '0ac9fcd7-73f0-47b1-8fbc-3948863e7a89',
},
{
PurchaseInvoice_id: '8e54a096-568b-48d9-8461-826be53a32da',
PurchaseInvoicePosition_id: '65e013a7-c7b2-47cf-88b7-2ab2d9bcd191',
ProductStock_id: null,
},
{
PurchaseInvoice_id: '8e54a096-568b-48d9-8461-826be53a32da',
PurchaseInvoicePosition_id: '8f00dde6-2548-46a7-a480-37e86a3ca895',
ProductStock_id: '1439dde4-d184-4c98-b0c4-6d3c88ce8496',
},
{
PurchaseInvoice_id: '8e54a096-568b-48d9-8461-826be53a32da',
PurchaseInvoicePosition_id: 'b48711b3-14b1-41ce-9f5f-4032297c1b8e',
ProductStock_id: null,
},
{
PurchaseInvoice_id: '8e54a096-568b-48d9-8461-826be53a32da',
PurchaseInvoicePosition_id: '4e22378d-cf56-4806-bea2-5ba0b220d3eb',
ProductStock_id: null,
},
];
I'd like to convert input object into output object as follows:
const 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',
},
],
},
{
PurchaseInvoicePosition_id: '65e013a7-c7b2-47cf-88b7-2ab2d9bcd191',
ProductStock_ids: [
{
ProductStock_id: null,
},
],
},
{
PurchaseInvoicePosition_id: '8f00dde6-2548-46a7-a480-37e86a3ca895',
ProductStock_ids: [
{
ProductStock_id: '1439dde4-d184-4c98-b0c4-6d3c88ce8496',
},
],
},
{
PurchaseInvoicePosition_id: 'b48711b3-14b1-41ce-9f5f-4032297c1b8e',
ProductStock_ids: [
{
ProductStock_id: null,
},
],
},
{
PurchaseInvoicePosition_id: '4e22378d-cf56-4806-bea2-5ba0b220d3eb',
ProductStock_ids: [
{
ProductStock_id: null,
},
],
},
],
},
];
Which should looks like on this image when folded:
And I don't know how to convert this way. I'm rather PHP developer, so doing this in JavaScript is hard for me.
I tried do this using some ways like three time iterate over this input object, searching uuid in arrays with no luck.
This input object can have multiple PurchaseInvoice_id which are connected with PurchaseInvoicePosition_id and with ProductStock_id.
CodePudding user response:
I'm going to call the operation you're performing nestedGroups()
. If you have an array of objects arr
, and an ordered list of keys key1
, key2
, key3
(etc.) of the elements of arr
, then nestedGroups(arr, key1, key2, key3)
will return an object whose key will be a pluralized (just adding "s"
) version of key1
, and whose value will be another array of groupings for the next key, and so on. This is hard to describe in words, so I'll just show an example:
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}]}
]
} */
If we can implement and strongly type nestedGroups()
, then your output will look like:
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;
}[];
}[];
}[] */
which you can verify is the type you want.
First let's implement it and worry about the strong types later:
// 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) }))
}
}
This is a recursive function which calls nestedGroups()
inside itself unless the array of keys
is empty. An empty keys
array results in an empty return object. Otherwise, we use the first key firstKey
to group the values in the array. Here I'm using a Map
to store the values, so each time we see a property value at key1
that we've seen before, we push it onto the end of the array in that Map
. And finally, the returned object has a single pluralized firstKey
property whose value is an array of objects composed of information from the Map
and from the recursive subcall to nestedGroups()
.
As for the typings, you'll end up needing recursive conditional types (to represent the recursion), variadic tuple types (to pull the array of keys apart) and template literal types (to add that "s"
in the type system).
Let's define the NestedGroups<T, K>
type to be the type of the return value of nestedGroups(arr, ...keys)
when arr
is of type T[]
and keys
is of type K
:
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;
We are constraining T
to have keys in K
and whose values at those keys are Primitive
values (which we define as the union of the types of JavaScript primitives) since those are easily comparable as Map
keys. (If the different values of, say, PurchaseInvoicePosition_id
were objects, then they probably wouldn't compare as equal, and it would be hard to group by them. Note that "x" === "x"
is true but {x: 1} === {x: 1}
is false).
Anyway, we will use NestedGroups<T, K>
as the return value for nestedGroups()
. We can keep the original implementation and just declare that nestedGroups
is an overloaded function with a single call signature:
// call signature
function nestedGroups<T extends Record<K[number], Primitive>, K extends string[]>
(arr: T[], ...keys: K): NestedGroups<T, K>;
Okay, time to test it on your input
:
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"
}
]
},
{
"PurchaseInvoicePosition_id": "65e013a7-c7b2-47cf-88b7-2ab2d9bcd191",
"ProductStock_ids": [
{
"ProductStock_id": null
}
]
},
{
"PurchaseInvoicePosition_id": "8f00dde6-2548-46a7-a480-37e86a3ca895",
"ProductStock_ids": [
{
"ProductStock_id": "1439dde4-d184-4c98-b0c4-6d3c88ce8496"
}
]
},
{
"PurchaseInvoicePosition_id": "b48711b3-14b1-41ce-9f5f-4032297c1b8e",
"ProductStock_ids": [
{
"ProductStock_id": null
}
]
},
{
"PurchaseInvoicePosition_id": "4e22378d-cf56-4806-bea2-5ba0b220d3eb",
"ProductStock_ids": [
{
"ProductStock_id": null
}
]
}
]
*/
Yes, that's the right type and the right output value.
There you go. The strong typing here might be overkill for your use case, but since you're asking about TypeScript and not just JavaScript, it might be useful to include it. If all you care about is the output value and not really the type, then you can forgo the typing and just leave it something wide like any
. It's up to you.
CodePudding user response:
Please find the Array.reduce
implementation of your question
Logic
- Loop through the input array.
- Check if there is already a node with the current
PurchaseInvoice_id
present in theaccumulator
- If its not present, push the current node to
accumulator
in the desired pattern (Check Section 1 in the comment added in code) - If its present (Section 2 in code comment), find whether there is a node with current
PurchaseInvoicePosition_id
in thePurchaseInvoicePosition_ids
of the found node - If that is not found (Section 3 in code comment), push your current
PurchaseInvoicePosition_id
andProductStock_id
to your accumulator. - If that is found (Section 4 in code comment) push your
ProductStock_id
to theProductStock_ids
of the found node.
Working Fiddle
const input = [
{ PurchaseInvoice_id: '8e54a096-568b-48d9-8461-826be53a32da', PurchaseInvoicePosition_id: '44edfd7f-bc9e-4155-ad5c-5dace9c7c31a', ProductStock_id: '0a701dbc-2661-4d67-b764-632cfb67334f', },
{ PurchaseInvoice_id: '8e54a096-568b-48d9-8461-826be53a32da', PurchaseInvoicePosition_id: '44edfd7f-bc9e-4155-ad5c-5dace9c7c31a', ProductStock_id: '15278807-794a-4727-9bcb-f7f68dfb4d41', },
{ PurchaseInvoice_id: '8e54a096-568b-48d9-8461-826be53a32da', PurchaseInvoicePosition_id: '44edfd7f-bc9e-4155-ad5c-5dace9c7c31a', ProductStock_id: '0ac9fcd7-73f0-47b1-8fbc-3948863e7a89', },
{ PurchaseInvoice_id: '8e54a096-568b-48d9-8461-826be53a32da', PurchaseInvoicePosition_id: '65e013a7-c7b2-47cf-88b7-2ab2d9bcd191', ProductStock_id: null, },
{ PurchaseInvoice_id: '8e54a096-568b-48d9-8461-826be53a32da', PurchaseInvoicePosition_id: '8f00dde6-2548-46a7-a480-37e86a3ca895', ProductStock_id: '1439dde4-d184-4c98-b0c4-6d3c88ce8496', },
{ PurchaseInvoice_id: '8e54a096-568b-48d9-8461-826be53a32da', PurchaseInvoicePosition_id: 'b48711b3-14b1-41ce-9f5f-4032297c1b8e', ProductStock_id: null, },
{ PurchaseInvoice_id: '8e54a096-568b-48d9-8461-826be53a32da', PurchaseInvoicePosition_id: '4e22378d-cf56-4806-bea2-5ba0b220d3eb', ProductStock_id: null, },
];
const output = input.reduce((acc, curr) => {
const purchaseInvoiceNode = acc.find((item) => item.PurchaseInvoice_id === curr.PurchaseInvoice_id);
if (purchaseInvoiceNode) {
// Section 2
const purchaseInvoicePositionNode = purchaseInvoiceNode.PurchaseInvoicePosition_ids.find((item) => item.PurchaseInvoicePosition_id === curr.PurchaseInvoicePosition_id);
if(purchaseInvoicePositionNode) {
// Section 4
purchaseInvoicePositionNode.ProductStock_ids.push({
ProductStock_id: curr.ProductStock_id,
})
} else {
// Section 3
purchaseInvoiceNode.PurchaseInvoicePosition_ids.push({
PurchaseInvoicePosition_id: curr.PurchaseInvoicePosition_id,
ProductStock_ids: [
{
ProductStock_id: curr.ProductStock_id
}
]
})
}
} else {
// Section 1
acc.push({
PurchaseInvoice_id: curr.PurchaseInvoice_id,
PurchaseInvoicePosition_ids: [
{
PurchaseInvoicePosition_id: curr.PurchaseInvoicePosition_id,
ProductStock_ids: [
{
ProductStock_id: curr.ProductStock_id
}
]
}
]
})
}
return acc;
}, []);
console.log(output)