编写一个递归函数来映射所有级别的数据

Write a recursive function to map data for all levels

我有一个数据映射器函数,它采用 json 四个级别的数据,然后映射到不同的格式

输入JSON格式:

[{
    "InventoryLevel2Id": "1234",
    "InventoryLevel2Information": "Test Data",
    "InventoryLevel2Name": "Test Data",
    "InventoryLevel3s": [
        {
            "InventoryLevel3Id": "5678",
            "InventoryLevel3Name": "Inner data at 1",
            "InventoryLevel3Information": "Inner info at 1",
            "InventoryLevel4s": [
                {
                    "InventoryLevel4Id": "9101112",
                    "InventoryLevel4Name": "Inner data at 2",
                    "InventoryLevel4Information": "Inner info at 2",
                    "InventoryLevel5s": [
                        {
                            "InventoryLevel5Id": "131415",
                            "InventoryLevel5Name": "Inner data at 3",
                            "InventoryLevel5Information": "Inner info at 3",
                        }
                    ],
                }
            ]
        }
    ]
}]

输出JSON格式:

[{
    label1: 'TestData',
    label2: "Test data",
    uniquieId: "1234",
    innerState: {
        data : {
            label1: 'Inner data at 1',
            label2: "Inner info at 1",
            uniquieId: "5678",
            innerState: {
                data: {
                    label1: 'Inner data at 2',
                    label2: "Inner info at 2",
                    uniquieId: "9101112",
                    innerState: {
                        data: {
                            label1: 'Inner data at 3',
                            label2: "Inner info at 4",
                            uniquieId: "131415",
                        }
                    }
                }
            }
        }
    }
}]

为此,我编写了这个映射器函数。这工作正常,但我必须一次又一次地调用映射器。为了优化,我正在考虑创建一个递归函数,它将调用它自己,直到完成所有级别的映射。

const dataMapperFunc = (
    inputData
  ) => {
    const mappedData = inputData?.map((a) => ({
      label1: a.InventoryLevel2Name,
      label2: a.InventoryLevel2Information,
      uniquieId: a.InventoryLevel2Id,
      innerState: {
        data: a.InventoryLevel3s?.map((b) => ({
          label1: b.InventoryLevel3Name,
          label2: b.InventoryLevel3Information,
          uniquieId: b.InventoryLevel3Id,
          innerState: {
            data: b.InventoryLevel4s?.map(
              (c) => ({
                label1: c.InventoryLevel4Name,
                label2: c.InventoryLevel4Information,
                uniquieId: c.InventoryLevel4Id,
                innerState: {
                  data: c.InventoryLevel5s?.map(
                    (d) => ({
                      label1: d.InventoryLevel5Name,
                      label2:
                        d.InventoryLevel5Information,
                      uniquieId: d.InventoryLevel5Id,
                    })
                  ),
                },
              })
            ),
          },
        })),
      },
    }));
    return mappedData;
  };

这是我对映射器函数的尝试,它在 InventoryLevel3s 停止并且未完成。我遗漏了几个步骤,可以做得更好。

const dataMapperFuncTryReccursive = (inputData ) => {
  const mappedData = inputData?.map((a) =>
    mapper(
      a,
      "InventoryLevel2Name",
      "InventoryLevel2Information",
      "InventoryLevel3Id",
      "InventoryLevel3s"
    )
  );
  return mappedData;
};

const mapper = (entity, field1, field2, uniquieId, childEntityName) => {
  if (entity) {
    return {
      label1: entity[field1],
      label2: entity[field2],
      uniquieId: entity[uniquieId],
      concurrencyId: entity.concurrencyId,
      innerState: {
        data: entity[childEntityName]?.map((innerData) =>
          mapper(
            innerData,
            "InventoryLevel3Name",
            "InventoryLevel3Information",
            "InventoryLevel3Id",
            "InventoryLevel4s"
          )
        ),
      },
    };
  }
};

这是一个递归函数,它应该可以为您提供所需的结果。它通过替换输入对象的第一个键中的所有非数字值来提取当前级别编号,然后使用它来查找适当的属性以复制到输出:

const data = [{
  "InventoryLevel2Id": "1234",
  "InventoryLevel2Information": "Test Data",
  "InventoryLevel2Name": "Test Data",
  "InventoryLevel3s": [{
    "InventoryLevel3Id": "5678",
    "InventoryLevel3Name": "Inner data at 1",
    "InventoryLevel3Information": "Inner info at 1",
    "InventoryLevel4s": [{
      "InventoryLevel4Id": "9101112",
      "InventoryLevel4Name": "Inner data at 2",
      "InventoryLevel4Information": "Inner info at 2",
      "InventoryLevel5s": [{
        "InventoryLevel5Id": "131415",
        "InventoryLevel5Name": "Inner data at 3",
        "InventoryLevel5Information": "Inner info at 3",
      }],
    }]
  }]
}]

const mapdata = (data) => {
  let level = +Object.keys(data)[0].replace(/[^\d]/g, '');
  const obj = {
    label1: data['InventoryLevel' + level + 'Name'],
    label2: data['InventoryLevel' + level + 'Information'],
    uniqueId: data['InventoryLevel' + level + 'Id']
  };
  level++;
  if (data.hasOwnProperty('InventoryLevel' + level + 's')) {
    obj.innerState = data['InventoryLevel' + level + 's'].map(mapdata);
  }
  return obj;
}

out = data.map(mapdata);
console.log(out);

这是我的看法。只需在数字上匹配一个通配符,你就可以用一个小的递归函数来完成。

const data = [{
    "InventoryLevel2Id": "1234",
    "InventoryLevel2Information": "Test Data",
    "InventoryLevel2Name": "Test Data",
    "InventoryLevel3s": [
        {
            "InventoryLevel3Id": "5678",
            "InventoryLevel3Name": "Inner data at 1",
            "InventoryLevel3Information": "Inner info at 1",
            "InventoryLevel4s": [
                {
                    "InventoryLevel4Id": "9101112",
                    "InventoryLevel4Name": "Inner data at 2",
                    "InventoryLevel4Information": "Inner info at 2",
                    "InventoryLevel5s": [
                        {
                            "InventoryLevel5Id": "131415",
                            "InventoryLevel5Name": "Inner data at 3",
                            "InventoryLevel5Information": "Inner info at 3",
                        }
                    ],
                }
            ]
        }
    ]
}]

const mapRecursive = (inputData) =>
    inputData.map(item =>
        Object.keys(item).reduce((obj, key) => {
            if (key.match("InventoryLevel.Id")) return { ...obj, uniquieId: item[key] };
            if (key.match("InventoryLevel.Name")) return { ...obj, label1: item[key] };
            if (key.match("InventoryLevel.Information")) return { ...obj, label2: item[key] };
            if (key.match("InventoryLevel.s")) return { ...obj, innerState: { data: mapRecursive(item[key]) } };
        }, {})
    );

console.log(mapRecursive(data));

通过使用辅助函数使其更具声明性可能会有优势。

此版本使用正则表达式和新键之间的映射列表,并使用布尔值 属性 来记录重复出现的位置。使用 transform 辅助函数,此问题的代码如下所示:

const mapData = transform ([
  {oldKey: /^InventoryLevel\d+Id$/, newKey: 'uniqueId'},
  {oldKey: /^InventoryLevel\d+Name$/, newKey:'label1'},
  {oldKey: /^InventoryLevel\d+Information$/, newKey: 'label2'},
  {oldKey: /^InventoryLevel\d+s$/, newKey: 'innerState', recur: true},
])

当然 transform 功能现在对其他类似问题很有用。但这里重要的不仅仅是可重用性。更重要的是一方面将问题分解为节点转换和递归,另一方面分解为具体节点转换的细节。应该很清楚在哪里添加额外的节点。

下面是 transform 的实现:

const transform = (config) => (objs) =>
  objs .map ((obj) =>
    Object .fromEntries (Object .entries (obj) .flatMap (([k, v]) => { 
      const {newKey = '', recur = false} = config .find (({oldKey}) => oldKey .test (k))
      return newKey
        ? [[newKey, recur ? transform (config) (v) : v]]
        : []       
    }))
  )

const mapData = transform ([
  {oldKey: /^InventoryLevel\d+Id$/, newKey: 'uniqueId'},
  {oldKey: /^InventoryLevel\d+Name$/, newKey:'label1'},
  {oldKey: /^InventoryLevel\d+Information$/, newKey: 'label2'},
  {oldKey: /^InventoryLevel\d+s$/, newKey: 'innerState', recur: true},
])

const input = [{InventoryLevel2Id: "1234", InventoryLevel2Information: "Test Data", InventoryLevel2Name: "Test Data", InventoryLevel3s: [{InventoryLevel3Id: "5678", InventoryLevel3Name: "Inner data at 1", InventoryLevel3Information: "Inner info at 1", InventoryLevel4s: [{InventoryLevel4Id: "9101112", InventoryLevel4Name: "Inner data at 2", InventoryLevel4Information: "Inner info at 2", InventoryLevel5s: [{InventoryLevel5Id: "131415", InventoryLevel5Name: "Inner data at 3", InventoryLevel5Information: "Inner info at 3"}]}]}]}]

console .log (mapData (input))
.as-console-wrapper {max-height: 100% !important; top: 0}

请注意,输出与 Nick 的回答中的格式相匹配。请求的格式与注释“是的,每个级别可以有多个值。每个级别都是一个数组”不匹配,即使在删除 data 节点之后也是如此。关键是 innerState 元素是数组而不是普通对象。

transform 有足够的空间使它更通用。但有一个权衡。我们做的越通用,我们在配置中要做的就越多。为了比较,我的第一个版本看起来更像这样:

const transform = (config) => (objs) =>
  objs .map ((obj) => 
    Object .fromEntries (Object .entries (obj) .flatMap (([k, v]) => 
      (config .find (({test}) => test(k)) || {result: () => []}) .result (v)
    ))
  )

const mapData = transform ([
  {test: (k) => /^InventoryLevel\d+Id$/.test(k), result: (v) => [['uniqueId', v]]},
  {test: (k) => /^InventoryLevel\d+Name$/.test(k), result: (v) => [['label1', v]]},
  {test: (k) => /^InventoryLevel\d+Information$/.test(k), result: (v) => [['label2', v]]},
  {test: (k) => /^InventoryLevel\d+s$/.test(k), result: (v) => [['innerState', mapData (v)]]},
])

请注意 transform 更强大。它允许在配置中使用任意测试函数,并与可以创建任何必要节点的输出函数配对。例如,它可以将一个输入节点变成三个输出节点。但请注意配置数组要复杂得多。我们需要 test: (k) => /^InventoryLevel\d+Id$/.test(k) 而不是 {oldKey: /^InventoryLevel\d+Id$/。而不是 result: (v) => [['uniqueId', v]],我们有 result: (v) => [['uniqueId', v]]。此外,我们必须显式调用递归,而不是用布尔值 属性.

如果我们选择的话,我们也可以走另一个方向,以稍微复杂的 transform 功能为代价使配置更简单。也许这样会更好:

const transform = (config) => (objs) =>
  objs .map ((obj) =>
    Object .fromEntries (Object .entries (obj) .flatMap (([k, v]) => { 
      const {newKey, recur} = config .find (
        ({oldKey}) => new RegExp (`^${oldKey .replace ('#', '\d+')}$`) .test (k)
      )
      return newKey
        ? [[newKey, recur ? transform (config) (v) : v]]
        : []       
    }))
  )

const mapData = transform ([
  {oldKey: 'InventoryLevel#Id', newKey: 'uniqueId'},
  {oldKey: 'InventoryLevel#Name', newKey:'label1'},
  {oldKey: 'InventoryLevel#Information', newKey: 'label2'},
  {oldKey: 'InventoryLevel#s', newKey: 'innerState', recur: true},
])

现在我们的配置不需要了解正则表达式,我们可以简单地使用 # 通配符来表示一组数字。这使得 transform 更不通用且功能更弱,但它确实简化了配置。如果您确定永远不会重复使用 transform,这可能是一个不错的选择。