在jq中从另一个中减去一个json文件

时间:2017-08-17 04:08:29

标签: arrays json object jq subtraction

有没有办法比较jq中的两个json文件?具体来说,我希望能够从一个json文件中删除对象(如果它们出现在另一个json文件中)。基本上,从另一个文件中减去一个文件。如果我可以对此进行概括以便我可以定义对象的相等标准,那将是一个额外的好处,但这不是严格必要的,它可以严格基于相同的对象。

所以更一般的情况会是这样的。让我们说我有一个看起来像这样的文件:

[
  {
    "name": "Cynthia",
    "surname": "Craig",
    "isActive": true,
    "balance": "$2,426.88"
  },
  {
    "name": "Elise",
    "surname": "Long",
    "isActive": false,
    "balance": "$1,892.72"
  },
  {
    "name": "Hyde",
    "surname": "Adkins",
    "isActive": true,
    "balance": "$1,769.34"
  },
  {
    "name": "Matthews",
    "surname": "Jefferson",
    "isActive": true,
    "balance": "$1,991.42"
  },
  {
    "name": "Kris",
    "surname": "Norris",
    "isActive": false,
    "balance": "$2,137.11"
  }
]

我有第二个文件,如下所示:

[
  {
    "name": "Cynthia",
    "surname": "Craig"
  },
  {
    "name": "Kris",
    "surname": "Norris"
  }
] 

我想从名字和姓氏字段与第二个文件的对象匹配的第一个文件中删除任何对象,以便结果如下所示:

[
  {
    "name": "Elise",
    "surname": "Long",
    "isActive": false,
    "balance": "$1,892.72"
  },
  {
    "name": "Hyde",
    "surname": "Adkins",
    "isActive": true,
    "balance": "$1,769.34"
  },
  {
    "name": "Matthews",
    "surname": "Jefferson",
    "isActive": true,
    "balance": "$1,991.42"
  }
] 

3 个答案:

答案 0 :(得分:2)

以下解决方案旨在通用,高效且尽可能简单,但须遵守前两个目标。

泛型

对于通用性,让我们假设$ 1和$ 2是两个数组 JSON实体,我们希望在$ 1中找到这些项目$ x 这样($ x | filter)不会出现在map($ two | filter)中,其中filter是任意过滤器。 (在本例中,它是{surname, name}。)

该解决方案使用INDEX/1,在官方1.5版本发布后添加到jq中,因此我们首先重现其定义:

def INDEX(stream; idx_expr):
  reduce stream as $row ({};
    .[$row|idx_expr|
      if type != "string" then tojson
      else .
      end] |= $row);
def INDEX(idx_expr): INDEX(.[]; idx_expr);

效率

为了提高效率,我们需要使用JSON对象作为字典; 因为键必须是字符串,所以我们需要确保在转换对象时 对于字符串,对象被规范化。为此,我们将normalize定义如下:

# Normalize the input with respect to the order of keys in objects
def normalize:
  . as $in
  | if type == "object" then reduce keys[] as $key
         ( {}; . + { ($key):  ($in[$key] | normalize) } ) 
    elif type == "array" then map( normalize )
    else .
    end;

要构建字典,我们只需应用(normalize | tojson):

def todict(filter):
  INDEX(filter| normalize | tojson);

解决方案

解决方案现在非常简单:

# select those items from the input stream for which 
# (normalize|tojson) is NOT in dict:
def MINUS(filter; $dict):
 select( $dict[filter | normalize | tojson] | not);

def difference($one; $two; filter):
  ($two | todict(filter)) as $dict
  | $one[] | MINUS( filter; $dict );

difference( $one; $two; {surname, name} )

调用

$ jq -n --argfile one one.json --argfile two two.json -f difference.jq

答案 1 :(得分:1)

以下是使用pull/1062

中的def project(q): . as $in | reduce (q | if type == "object" then keys[] else .[] end) as $k ( {} ; . + { ($k) : ($in[$k]) } ) ; map( reduce $arg[] as $a ( . ; select(project($a) != $a) ) | values ) second.json的解决方案
data.json

如果你放置"第二个" filter.jq中的文件,jq -M --argfile arg second.json -f filter.jq data.json 中的数据以及[ { "name": "Elise", "surname": "Long", "isActive": false, "balance": "$1,892.72" }, { "name": "Hyde", "surname": "Adkins", "isActive": true, "balance": "$1,769.34" }, { "name": "Matthews", "surname": "Jefferson", "isActive": true, "balance": "$1,991.42" } ] 中的上述过滤器,您可以使用

运行此文件
select(project($a) != $a)

生产

project/1

如果要修改对象的相等条件,可以用其他内容替换表达式contains

考虑到这一点,我们可以通过 map( reduce $arg[] as $a ( . ; select(.!=null and contains($a)==false) ) | values ) 消除对any的需求。这应该更有效,因为它消除了临时对象的构造。

map(select(any(.; contains($arg[]))==false))

可以使用jq -M --argfile arg second.json 'map(select(any(.; contains($arg[]))==false))' data.json 进一步简化:

{{1}}

,足够短,可直接在命令行中使用:

{{1}}

答案 2 :(得分:1)

jq 解决方案:

jq --slurpfile s f2.json '[ .[] | . as $o | if (reduce $s[0][] as $i
     ([]; . + [($o | contains($i))]) | any) then empty else $o end ]' f1.json

输出:

[
  {
    "name": "Elise",
    "surname": "Long",
    "isActive": false,
    "balance": "$1,892.72"
  },
  {
    "name": "Hyde",
    "surname": "Adkins",
    "isActive": true,
    "balance": "$1,769.34"
  },
  {
    "name": "Matthews",
    "surname": "Jefferson",
    "isActive": true,
    "balance": "$1,991.42"
  }
]