【问题标题】:Write a recursive function to map data for all levels编写一个递归函数来映射所有级别的数据
【发布时间】:2021-03-25 16:25:53
【问题描述】:

我有一个数据映射器函数,它采用 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",
                        }
                    }
                }
            }
        }
    }
}]

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

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"
          )
        ),
      },
    };
  }
};

【问题讨论】:

  • 为什么data 元素在innerState 中有额外的嵌套级别?这似乎没有必要,因为它是唯一的。
  • 每个级别可以有多个值吗?样本数据只有一个。
  • @Nick,是的,每个级别可以有多个值。每个级别都是一个数组。而且,同意,我们不需要内部状态中的数据。

标签: javascript typescript recursion callback


【解决方案1】:

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

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);

【讨论】:

    【解决方案2】:

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

    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));

    【讨论】:

      【解决方案3】:

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

      此版本使用正则表达式和新键之间的映射列表,并带有一个布尔属性来记录重复出现的位置。使用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}

      请注意,输出与尼克回答中的格式匹配。请求的格式与“是的,每个级别可以有多个值。每个级别都是一个数组”的注释不匹配,即使在删除 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,这可能是一个不错的选择。

      【讨论】:

        猜你喜欢
        • 2021-08-13
        • 1970-01-01
        • 1970-01-01
        • 2012-10-28
        • 2017-06-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-04-19
        相关资源
        最近更新 更多