【问题标题】:Convert one object in another object in TypeScript在 TypeScript 中将一个对象转换为另一个对象
【发布时间】:2021-11-18 01:27:00
【问题描述】:

我有一个数据库结果,如文件所示:

所以我输入的数据如下:

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 = [
    {
        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,
                    },
                ],
            },
        ],
    },
];

折叠后的图像应该是这样的:

而且我不知道如何转换这种方式。我是 PHP 开发人员,所以用 JavaScript 做这件事对我来说很难。

我尝试使用一些方法来做到这一点,比如对这个输入对象进行三次迭代,在数组中搜索 uuid,但没有成功。

这个输入对象可以有多个 PurchaseInvoice_id 与 PurchaseInvoicePosition_id 和 ProductStock_id 相连。

【问题讨论】:

  • 到目前为止,您尝试过什么来解决它?你的尝试有什么问题?

标签: javascript arrays json typescript object


【解决方案1】:

请找到您的问题的Array.reduce 实现

逻辑

  • 循环输入数组。
  • 检查accumulator中是否已经存在当前PurchaseInvoice_id的节点
  • 如果不存在,则以所需的模式将当前节点推送到accumulator(检查代码中添加的注释中的第 1 节)
  • 如果存在(代码注释第2节),在找到的节点的PurchaseInvoicePosition_ids中查找当前PurchaseInvoicePosition_id的节点是否存在
  • 如果未找到(代码注释中的第 3 节),请将您当前的 PurchaseInvoicePosition_idProductStock_id 推送到您的累加器。
  • 如果找到(代码注释中的第 4 节)将您的 ProductStock_id 推送到找到的节点的 ProductStock_ids

工作小提琴

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)

【讨论】:

  • 非常感谢。我已经在我的代码中调整了您的解决方案。谢谢你:)
【解决方案2】:

我将调用您正在执行的操作nestedGroups()。如果您有一个对象数组arr,以及arr 的元素的键key1key2key3(等)的有序列表,那么nestedGroups(arr, key1, key2, key3) 将返回一个对象,其键将是key1 的复数(仅添加"s")版本,其值将是下一个键的另一个分组数组,依此类推。这很难用语言来描述,所以我只举一个例子:

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}]}
  ]
} */

如果我们可以实现并强类型nestedGroups(),那么您的输出将如下所示:

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;
        }[];
    }[];
}[] */

你可以验证的是你想要的类型。


首先让我们实现它,然后再考虑强类型:

// 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) }))
    }
}

这是一个递归函数,它在自身内部调用nestedGroups(),除非keys 的数组为空。一个空的keys 数组会导致一个空的返回对象。否则,我们使用第一个键 firstKey 对数组中的值进行分组。这里我使用Map 来存储值,所以每次我们在key1 看到一个我们以前见过的属性值,我们将它推到Map 的数组末尾。最后,返回的对象有一个复数形式的firstKey 属性,其值是由来自Map 和来自nestedGroups() 的递归子调用的信息组成的对象数组。


至于打字,你最终需要recursive conditional types(表示递归)、variadic tuple types(将键数组分开)和template literal types(在类型中添加"s"系统)。

arr 的类型为T[]keys 的类型为K 时,我们将NestedGroups&lt;T, K&gt; 类型定义为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;

我们是constrainingT,在K 中有键,并且这些键的值是Primitive 值(我们将其定义为JavaScript primitives 类型的union),因为这些是easily comparable as Map keys。 (如果 PurchaseInvoicePosition_id 的不同值是 objects,那么它们可能不会比较相等,并且很难按它们进行分组。请注意 "x" === "x" 是 true 但{x: 1} === {x: 1} 为假)。

无论如何,我们将使用NestedGroups&lt;T, K&gt; 作为nestedGroups() 的返回值。我们可以保留原来的实现,只声明nestedGroups 是一个带有单个调用签名的overloaded function

// call signature
function nestedGroups<T extends Record<K[number], Primitive>, K extends string[]>
    (arr: T[], ...keys: K): NestedGroups<T, K>;

好的,是时候在你的 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
        }
      ]
    }
  ]
*/

是的,这是正确的类型和正确的输出值。


给你。这里的强类型对于您的用例来说可能是多余的,但由于您询问的是 TypeScript 而不仅仅是 JavaScript,因此包含它可能会很有用。如果您只关心输出值而不是真正的类型,那么您可以放弃输入,并将其保留为 any 之类的宽泛的内容。这取决于你。

Playground link to code

【讨论】:

  • 非常感谢。由于类型的原因,我无法调整您的版本。我选择并调整了 Nitheesh 的解决方案。
猜你喜欢
  • 2021-08-14
  • 1970-01-01
  • 1970-01-01
  • 2017-10-15
  • 2015-09-03
  • 2018-02-10
  • 1970-01-01
  • 2013-09-18
  • 1970-01-01
相关资源
最近更新 更多