[英]subtracting one json file from another in jq
有没有办法比较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"
}
]
根据前两个目标,以下解决方案旨在变得通用,高效且尽可能简单。
为了通用起见,让我们假设$ one和$ two是JSON实体的两个数组,并且我们希望在$ one中找到这些项$ 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
这是使用来自pull / 1062的 --argfile
和project/1
的解决方案
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)
替换为其他内容。
再多考虑一下,我们可以通过使用contains
来消除对project/1
的需要。 这应该更有效,因为它消除了临时对象的构造。
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
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"
}
]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.