jq:从数组中删除与基于同一数组中其他项的条件匹配的项

时间:2018-02-16 04:35:36

标签: json jq

我需要一些有点复杂的jq查询的帮助。

鉴于

[{
  "type": "ITEM_PURCHASED",
  "timestamp": 1710829,
  "participantId": 2,
  "itemId": 3089
},
{
  "type": "ITEM_PURCHASED",
  "timestamp": 1711620,
  "participantId": 7,
  "itemId": 2055
},
{
  "type": "ITEM_PURCHASED",
  "timestamp": 1711621,
  "participantId": 7,
  "itemId": 1058
},
{
  "type": "ITEM_PURCHASED",
  "timestamp": 1714435,
  "participantId": 9,
  "itemId": 1037
},
{
  "type": "ITEM_UNDO",
  "timestamp": 1716107,
  "participantId": 7,
  "afterId": 0,
  "beforeId": 2055
},
{
  "type": "ITEM_UNDO",
  "timestamp": 1716272,
  "participantId": 7,
  "afterId": 0,
  "beforeId": 1058
},
{
  "type": "ITEM_PURCHASED",
  "timestamp": 1718091,
  "participantId": 7,
  "itemId": 1026
}]

期望的输出:

[{
  "type": "ITEM_PURCHASED",
  "timestamp": 1710829,
  "participantId": 2,
  "itemId": 3089
},
{
  "type": "ITEM_PURCHASED",
  "timestamp": 1714435,
  "participantId": 9,
  "itemId": 1037
},
{
  "type": "ITEM_PURCHASED",
  "timestamp": 1718091,
  "participantId": 7,
  "itemId": 1026
}]

我想过滤此数组并删除所有已购买的商品"撤消"。 PURCHASE_ITEM对象可以通过在其后面添加ITEM_UNDONE对象来撤消,该对象具有更高的时间戳,匹配的participantId和beforeId == itemId。

我尝试了以下方法:

  1. 收集所有ITEM_UNDO对象
  2. 查找所有相应的ITEM_PURCHASED对象
  3. 从原始列表中减去
  4. 第2步给我带来麻烦。 到目前为止,我有以下代码不起作用:

    jq '
    map(select(.type=="ITEM_UNDO")) as $undos |
     [
        {
          undo: $undos[],
          before_purchases: map( select(.type=="ITEM_PURCHASED"
                                        and .itemId == $undos[].beforeId
                                        and .participantId == $undos[].participantId
                                        )
    
                               )
        }
      ] as $undo_with_purchased | $undo_with_purchased
    '
    

    我知道为什么它不起作用,因为在行

    and .itemId == $undos[].beforeId
    and .participantId == $undos[].participantId
    

    $ undos独立扩展两次,而不是每次比较使用相同的实例,然后在

    中第三次使用
    undo: $undos[],
    

    我似乎无法找到一种强制方法来强制jq仅迭代$ undos一次并使用相同的实例进行所有比较。通常,我在同时迭代多个阵列时会遇到问题,执行操作。在任何程序语言中,这都不是一件容易的事,但在jq中做这种事情的最佳方法是什么?

    感谢您的任何建议!

1 个答案:

答案 0 :(得分:3)

首先,让我们定义一个过滤器,它会判断数组中的某个项目是否已被撤消"通过后续(在数组和时间中)项。这可以直接使用any/2

# input: the entire array
# output: true iff item n is "undone" by a subsequent item
def undone($n):
  . as $in
  | length as $length
  | .[$n] as $nth
  | if $nth.type != "ITEM_PURCHASED" then false
    else any( range($n+1; $length) | $in[.]; 
              .type == "ITEM_UNDO"
              and .participantId == $nth.participantId
              and .beforeId== $nth.itemId
              and .timestamp > $nth.timestamp)
    end;

现在查询非常简单:

[ range(0;length) as $i
  | select( (.[$i].type == "ITEM_PURCHASED") and (undone($i) | not) )
  | .[$i] ]

调用:jq -f program.jq data.json

输出:包含三个项目的数组。

风格

可以写:

range($n+1; $length) | $in[.]

更紧凑,也许更具惯用性,如:

$in[range($n+1; $length)]

事实上,$in$length都可以完全取消,所以问题的片段会变得简单:

.[range($n+1; length)]