动态遍历深层嵌套对象并累积结果

Dynamically traversing a deep nested object and accumulating results

我正在尝试剖析一个深度嵌套的对象。遍历它并将数据拉到一个单一级别的对象,然后与一个更大的对象结合起来。我可以使用以下代码遍历它,但它不是动态的。就像,这只有在我深入了解它的 4 个对象时才有效。实际上它可以是 1 -> n 深。我还有一个函数可以检查该级别的值类型并相应地对其进行处理。现在这对我有用

    run();
function run() {
  const topLevelObjects = [{
      field: 'pivotvalue0',
      value: '111111111',
      count: 36,
      pivot: [{
          field: 'pivotvalue1',
          value: 'Y',
          count: 27,
          pivot: [{
            field: 'pivotvalue2',
            value: 'Header1',
            count: 27,
            pivot: [{
                field: 'pivotvalue3',
                value: 'Value1',
                count: 14
              },
              {
                field: 'pivotvalue3',
                value: 'Value2',
                count: 13
              }
            ]
          }]
        },
        {
          field: 'pivotvalue1',
          value: 'Z',
          count: 9,
          pivot: [{
            field: 'pivotvalue2',
            value: 'Header2',
            count: 9,
            pivot: [{
              field: 'pivotvalue3',
              value: 'Value1',
              count: 9
            }]
          }]
        }
      ]
    },
    {
      field: 'pivotvalue0',
      value: '222222222',
      count: 23,
      pivot: [{
        field: 'pivotvalue1',
        value: 'Y',
        count: 23,
        pivot: [{
            field: 'pivotvalue2',
            value: 'Header1',
            count: 12,
            pivot: [{
              field: 'pivotvalue3',
              value: 'Value2',
              count: 12
            }]
          },
          {
            field: 'pivotvalue2',
            value: 'Header2',
            count: 11,
            pivot: [{
              field: 'pivotvalue3',
              value: 'Value2',
              count: 11
            }]
          }
        ]
      }]
    }
  ]

  //pivotResponse.facet_counts.facet_pivot[pivotsString]
  const accumulatedPivotInfo = []
  //This is assuming you can only pivot 4 deep. Would love to figure out how to make this dynamic.
  for (const level0 of topLevelObjects) {
    let newPivotInfo = {}

    newPivotInfo[level0.field] = level0.value
    if (level0.pivot) {
      for (const level1 of level0.pivot) {
        newPivotInfo = {
          ...newPivotInfo,
          ...buildBasedOnType(level1, newPivotInfo)
        }
        if (level1.pivot) {
          for (const level2 of level1.pivot) {
            newPivotInfo = {
              ...newPivotInfo,
              ...buildBasedOnType(level2, newPivotInfo)
            }
            if (level2.pivot) {
              for (const level3 of level2.pivot) {
                newPivotInfo = {
                  ...newPivotInfo,
                  ...buildBasedOnType(level3, newPivotInfo)
                }
              }
            }
          }
        }
      }
    }
    accumulatedPivotInfo.push(newPivotInfo)

  }
  console.log(accumulatedPivotInfo)
}

function buildBasedOnType(level, newPivotInfo) {
  if (level.field === 'pivotvalue1') {
    level.value === 'Y' ? newPivotInfo['pivotvalue1.1'] = level.count : newPivotInfo['pivotvalue1.1'] = 0
    level.value === 'N' ? newPivotInfo['pivotvalue1.2'] = level.count : newPivotInfo['pivotvalue1.2'] = 0
    level.value === 'Z' ? newPivotInfo['pivotvalue1.3'] = level.count : newPivotInfo['pivotvalue1.3'] = 0
  } else if (level.field === 'pivotvalue2' || level.field === 'pivotvalue3') {
    newPivotInfo[level.field + 's'] === undefined ? newPivotInfo[level.field + 's'] = new Set([level.value]) : newPivotInfo[level.field + 's'].add(level.value)
  } else {
    newPivotInfo[level.field] = level.value
  }
  return newPivotInfo
}


这是我的最终输出结果以及我要实现的目标

pivotInfo = [
  {
    pivotvalue0: '111111111',
    'pivotvalue1.1': 0,
    'pivotvalue1.2': 0,
    'pivotvalue1.3': 9,
    pivotvalue2s: Set { 'Header1', 'Header2' },
    pivotvalue3s: Set { 'Value1', 'Value2' }
  },
  {
    pivotvalue0: '222222222',
    'pivotvalue1.1': 23,
    'pivotvalue1.2': 0,
    'pivotvalue1.3': 0,
    pivotvalue2s: Set { 'Header1', 'Header2' },
    pivotvalue3s: Set { 'Value2' }
  }
]

出于某种原因,代码运行器确实可以很好地处理集合。所以这是 jsfiddle 中设置集合的相同代码:https://jsfiddle.net/x9sa8tqm/

希望这是足够的上下文来了解我目前在做什么。现在我有数据可以通过值数组了解这些对象的深度。所以我可以检查数组长度并知道我需要走多深。使这种动态化的最佳方法是什么?所以它会弯曲到 'n' 深度而不是头部编码 4.

谢谢!

使用递归实现。

var data = [
    {
      field: 'pivotvalue0',
      value: '200275399',
      count: 36,
      pivot: [{
      field: 'pivotvalue0.1',
      value: '200275399',
      count: 36,
      pivot: [{
      field: 'pivotvalue0.2',
      value: '200275399',
      count: 36,
      pivot: []
    }]
    }]
    },
    {
      field: 'pivotvalue0',
      value: '200746617',
      count: 23,
      pivot: []
    }
  ]

var finalResult = [];

function accumulateValue(data){
  data.forEach(item=>{
    finalResult.push({
      field: item.field,
      value: item.value,
      count: item.count,
    });
    if(item.pivot && item.pivot.length){
      accumulateValue(item.pivot)
    }
  })
}

accumulateValue(data)

console.log(finalResult);

这是一种递归执行此操作的方法。我们 special-case 您想要专门处理的类型,对于所有其他情况,我们将 {[field]: value} 添加到我们的 运行ning 输出 object.

要注意的主要事情是递归是自动的。我们不手动编码关卡,这应该让我们可以任意深入。

// utility function
const gather = (prop) => (fn) => (o) => 
  [... fn (o), ... (prop in o ? o [prop] : []) .flatMap (gather (prop) (fn))]

// helper function
const collectField = (name) => (o) => ({
  [name + 's'] : 
    [... // comment out to get an actual set
      new Set(
        gather ('pivot') (
          ({field, value}) => field == name ? [value] : []
        ) (o)
      )
    ]  // comment out to get an actual set
})

// main function
const convert = (xs) => 
  xs .map ((x, _, __, {field, value, count, pivot = []} = x) => Object .assign ({
    ...(
      field == 'pivotvalue1'
        ? {
            'pivotvalue1.1': value == 'Y' ? count: 0,
            'pivotvalue1.2': value == 'N' ? count: 0,
            'pivotvalue1.3': value == 'Z' ? count: 0,
          }
      : field == 'pivotvalue2' || field == 'pivotvalue3'
        ? {}
      : {[field]: value}
    )}, 
    ... convert (pivot),
    collectField ('pivotvalue2') (x),                                                             
    collectField ('pivotvalue3') (x),                
  ))

// sample data
const topLevelObjects = [{field: "pivotvalue0", value: "111111111", count: 36, pivot: [{field: "pivotvalue1", value: "Y", count: 27, pivot: [{field: "pivotvalue2", value: "Header1", count: 27, pivot: [{field: "pivotvalue3", value: "Value1", count: 14}, {field: "pivotvalue3", value: "Value2", count: 13}]}]}, {field: "pivotvalue1", value: "Z", count: 9, pivot: [{field: "pivotvalue2", value: "Header2", count: 9, pivot: [{field: "pivotvalue3", value: "Value1", count: 9}]}]}]}, {field: "pivotvalue0", value: "222222222", count: 23, pivot: [{field: "pivotvalue1", value: "Y", count: 23, pivot: [{field: "pivotvalue2", value: "Header1", count: 12, pivot: [{field: "pivotvalue3", value: "Value2", count: 12}]}, {field: "pivotvalue2", value: "Header2", count: 11, pivot: [{field: "pivotvalue3", value: "Value2", count: 11}]}]}]}]

// demo
console .log (convert (topLevelObjects))
.as-console-wrapper {max-height: 100% !important; top: 0}

gather 是一个相当通用的辅助函数,用于递归地收集将函数应用于每个节点的结果。初始参数 prop 用于 属性 保存树的更深层的节点的名称(最常见的是 'children',这里是 'pivot'。)第二个参数是一个函数,其每个节点的结果将在最后收集到一个数组中。当然,第三个参数是你的树 object 本身。这是我在这里使用的常用函数的轻微变体,因为它在将每次调用的结果包含在数组中之前将其展平。这允许提供给 return 具有单个值或没有值的数组的函数,并且输出数组仅包含实际值。 (如果我们想使用更通用的 gather,有很多替代方法可以做这样的事情。)

我们在collectField中使用gather来获取所有关联的value节点,当field属性 匹配提供的 name。请注意,我们不会在下面函数的主要递归调用中 运行 this 。这些结果将在稍后附上。虽然很可能通过深度递归调用来传递状态信息,但如果可以的话,我宁愿避免它。这意味着对于 pivotvalue2pivotvalue3,我们实际上进行了单独的递归。另请注意,您在此处的输出中请求 Set,我使用数组是为了更轻松地进行演示。但我注意到您可以注释掉这两行以使其保持实际设置。

main函数将指定的字段收集到一个object中,然后在pivot中对children进行递归调用,然后将这两个调用添加到[=19] =].

这对于性能来说并不理想,因为我们对输入进行多次递归遍历。但它是更简单的代码,如果存在可衡量的性能问题,我们可以稍后再回来。

(稍后我可能会考虑另一种方法,因为我有一个想法,但现在没有时间。)