Home > Enterprise >  Convert one object in another object in TypeScript
Convert one object in another object in TypeScript

Time:09-25

I have an database result as shows in file:

database results

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:

outout result

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.

Playground link to code

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 the accumulator
  • 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 the PurchaseInvoicePosition_ids of the found node
  • If that is not found (Section 3 in code comment), push your current PurchaseInvoicePosition_id and ProductStock_id to your accumulator.
  • If that is found (Section 4 in code comment) push your ProductStock_id to the ProductStock_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)

  • Related