如何提取和处理嵌套数组?

How to extract and process nested arrays?

我正在努力从 JSON 测试报告中提取规则数组。谁能指出我正确的方向?

// input
const json = {
"data": {
    "allTests": [
        {
          "id": "foo",
          "testStatus": "PASS",
          "ruleList": [
            {
              "outcome": "PASS",
              "name": "Image should be awesome"
            }
          ]
        },
        {
          "id": "bar",
          "testStatus": "FAIL",
          "ruleList": [
            {
              "outcome": "HARD_FAIL",
              "name": "Image should be awesome"
            }
          ]
        },
        {
          "id": "baz",
          "testStatus": "FAIL",
          "ruleList": [
            {
              "outcome": "SOFT_FAIL",
              "name": "Image should be awesome"
            }
          ]
        },
    ]
  }
}

预期结果:

[{
    "name": "Image should be awesome",
    "HARD_FAIL": 1,
    "SOFT_FAIL": 1,
    "PASS": 1
}]

(冒昧只用json.data.allTests

我会做什么:

  1. chain
  2. 提取所有ruleList中的所有规则
  3. 当你这样做时,还原 outcome 属性 例如{outcome: 'PASS'} => {PASS: 1}
  4. name 分组,总结所有结果 (假设例如 PASS: 2 是可能的)
  5. 提取所有值

const with_ramda =
  pipe(
    chain(x => x.ruleList.map(({outcome, name}) => ({[outcome]: 1, name}))),
    reduceBy(({name: _, ...acc}, x) => mergeWith(add, acc, x), {}, prop('name')),
    values);
        
console.log(with_ramda(input));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>

<script>const {pipe, chain, reduceBy, mergeWith, add, prop, values} = R;</script>

<script>
const input =
  [ { "id": "foo"
    , "testStatus": "PASS"
    , "ruleList":
        [ { "outcome": "PASS"
          , "name": "Image should be awesome"
          }
        ]
    }
  , { "id": "bar"
    , "testStatus": "FAIL"
    , "ruleList":
        [ { "outcome": "HARD_FAIL"
          , "name": "Image should be awesome"
          }
        ]
    }
  , { "id": "baz"
    , "testStatus": "FAIL"
    , "ruleList":
        [ { "outcome": "SOFT_FAIL"
          , "name": "Image should be awesome"
          }
        ]
    }
  ];
</script>


如果您有兴趣,也可以使用原始解决方案,而且不一定更复杂:

const with_vanillajs =
  xs =>
    Object.values(
      xs.flatMap(x => x.ruleList)
        .reduce((acc, {outcome, name}) =>
          ( acc[name] = acc[name] || {name}
          , acc[name][outcome] = (acc[name][outcome] || 0) + 1
          , acc), {}));
          

console.log(with_vanillajs(input));
<script>
const input =
  [ { "id": "foo"
    , "testStatus": "PASS"
    , "ruleList":
        [ { "outcome": "PASS"
          , "name": "Image should be awesome"
          }
        ]
    }
  , { "id": "bar"
    , "testStatus": "FAIL"
    , "ruleList":
        [ { "outcome": "HARD_FAIL"
          , "name": "Image should be awesome"
          }
        ]
    }
  , { "id": "baz"
    , "testStatus": "FAIL"
    , "ruleList":
        [ { "outcome": "SOFT_FAIL"
          , "name": "Image should be awesome"
          }
        ]
    }
  ];
</script>

嗯,这可能行得通:

var Result = [{}];
json.data.allTests.forEach (Entry => {
    var Rules = Entry.ruleList || [];

    Rules.forEach (Rule => {
        var { outcome, name } = Rule;

        console.log ({ outcome, name });

        Result [ 0 ] [ outcome ] = 1;
        Result [ 0 ].name = name;
    });
});
console.log ({ Result });

使用 R.chain 展平 ruleList 数组,按 name 对它们进行分组,然后转换为 [name, array of rules] 对,映射这些对,并将每对转换为目的。使用 R.countBy 计算结果的分数:

const { countBy, prope, pipe, chain, prop, groupBy, toPairs, map } = R

const countByOutcome = countBy(prop('outcome'))

const fn = pipe(
  chain(prop('ruleList')), // flatten the ruleList
  groupBy(prop('name')), // group by the name
  toPairs, // convert to [name, values] pairs
  map(([name, val]) => ({ // map the pairs to objects 
    name,
    ...countByOutcome(val) // count the outcomes
  })),
)

const input = [{"id":"foo","testStatus":"PASS","ruleList":[{"outcome":"PASS","name":"Image should be awesome"}]},{"id":"bar","testStatus":"FAIL","ruleList":[{"outcome":"HARD_FAIL","name":"Image should be awesome"}]},{"id":"baz","testStatus":"FAIL","ruleList":[{"outcome":"SOFT_FAIL","name":"Image should be awesome"}]}]
        
console.log(fn(input))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>

添加一个类似于 Ori Drori 的答案,因为它足够不同,很有趣。

首先,您给出的一个示例确实不足以准确说明您想做什么,但其他几个答案也做出了与我相同的假设,所以我猜我们没问题。但明确地说,我假设:

  • 结果中的 1 是计数,而不是简单地表示包含结果的布尔标记。我的测试数据通过包含名称“Image should be awesome”的第二个“PASS”场景来检查这一点。
  • 数据中可以有多个name。您的示例仅显示一个。我在我的测试用例中添加了另一个。
  • 输出对象按这些名称分组,而不是按例如与 allTests 平行的其他字段分组。

基于这些假设,我写了这个:

const transform = pipe (
  path (['data', 'allTests']),
  chain (prop ('ruleList')),
  groupBy (prop ('name')),
  map (pluck ('outcome')),
  map (countBy (identity)),
  toPairs,
  map (([name, rest]) => ({name, ...rest})),
)

const json = {data: {allTests: [{id: "foo", testStatus: "PASS", ruleList: [{outcome: "PASS", name: "Image should be awesome"}, {outcome: "HARD_FAIL", name: "Image should be chocolate"}]}, {id: "bar", testStatus: "FAIL", ruleList: [{outcome: "HARD_FAIL", name: "Image should be awesome"}]}, {id: "baz", testStatus: "FAIL", ruleList: [{outcome: "SOFT_FAIL", name: "Image should be awesome"}, {outcome: "PASS", name: "Image should be awesome"}]}]}}

console .log (transform (json))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script> const {pipe, path, chain, prop, groupBy, map, pluck, countBy, identity, toPairs} = R </script>

我倾向于一步一步地编写转换函数,每次都试图让我的输出更接近我的最终目标。所以我写了这篇文章。可能有更简单的方法来更改这些或组合步骤。 Ori Drori 提取了一个有用的函数,我们也可以在这里这样做。但我认为它读起来相当不错,您可以通过注释掉管道中函数的任何尾部来查看中间结果来检查它的作用。

如果你是无点恋者,可以将函数的最后一行替换为map (apply (useWith (mergeRight, [objOf('name')])))。我看不出有必要,但这会导致完全无意义的解决方案。