我想在 JQ if 语句中交换两个值。
给定以下 JSON:
{
"food": {
"fruit": [
{
"type": "apple",
"count": 0
},
{
"type": "banana",
"count": 1
},
{
"type": "orange",
"count": 0
}
]
}
}
我想使用 JQ 根据条件交换两个对象的计数以产生以下结果。
{
"food": {
"fruit": [
{
"type": "apple",
"count": 1
},
{
"type": "banana",
"count": 0
},
{
"type": "orange",
"count": 0
}
]
}
}
到目前为止,我可以将第一个水果从 0 修正为 1
jq '
if .food.fruit[] | select(.type=="apple") | .count == 0
then
.food.fruit[] | select(.type=="apple") | .count = 1
else
empty
end
'
但我找不到正确的操作符来修改第二个。是否可以使用 JQ 进行以下操作?
jq '
if .food.fruit[] | select(.type=="apple") | .count == 0
then
.food.fruit[] | select(.type=="apple") | .count = 1 &
.food.fruit[] | select(.type=="banana") | .count = 0
else
empty
end
'
我无法通过管道传输它,因为这会将单个水果对象通过管道传输到下一行,所以我不确定我应该在这里使用哪个运算符 - 如果甚至支持此类功能。 非常感谢任何帮助!
答案 0 :(得分:3)
这是一个高效的解决方案,它甚至可能不需要完全遍历一次 fruit
数组。
这个想法是找到“apple”和“banana”对象的路径,这样交换就可以在没有任何进一步迭代的情况下完成。
为了提高效率,该解决方案使用 foreach
以便在找到“apple”和“banana”对象的路径后跳出(单个)循环。
.food.fruit |=
( . as $fruits
| label $go
| (foreach paths(objects) as $p ({};
($fruits|getpath($p)) as $x
| if ($x|.type)=="apple"
then .apple = {path: ($p + ["count"]), count: $x.count}
elif ($x|.type) == "banana"
then .banana = {path: ($p + ["count"]), count: $x.count}
else . end;
if .apple and .banana then ., break $go else empty end)
) as $dict
| if $dict | .apple and .banana
then setpath( $dict.apple.path; $dict.banana.count)
| setpath( $dict.banana.path; $dict.apple.count)
else . end
)
上述解决方案可以抽象为:
# Input: an array of JSON objects.
#
# In the objects with .[$f1key] == $f1 and .[$f1key] == $f2 if any,
# swap the values at .[$gkey].
# If the the array has more than one object with .[$f1key] == $f1,
# then the last one will be selected, and similarly with respect to $f2
#
def swap($f1; $f2; $fkey; $gkey):
. as $in
| label $go
| (foreach paths(objects) as $p ({};
($in|getpath($p)) as $x
| if ($x[$fkey])==$f1
then .f1 = {path: ($p + [$gkey]), count: $x[$gkey]}
elif ($x[$fkey]) == $f2
then .f2 = {path: ($p + [$gkey]), count: $x[$gkey]}
else . end;
if .f1 and .f2 then ., break $go else empty end)
) as $dict
| if $dict | .f1 and .f2
then setpath( $dict.f1.path; $dict.f2.count)
| setpath( $dict.f2.path; $dict.f1.count)
else . end ;
.food.fruit |= swap("apple"; "banana"; "type"; "count")
答案 1 :(得分:2)
这是一个简单的解决方案,它只使用内置函数,并展示了如何使用“|”将更新链接在一起(*)。
.food.fruit |=
(map(.type) as $types
| ($types | index("apple")) as $ia
| ($types | index("banana")) as $ib
| .[$ia].count as $ac
| .[$ia].count = .[$ib].count
| .[$ib].count = $ac
)
这里有一个类似的解决方案,它避免了 index
的问题,并且由于不使用 map
而更加节省空间。
# index of $needle in a stream
def index_of($needle; stream):
label $go
| foreach stream as $x (-1; .+1; select($x==$needle) | (., break $go))
// null;
.food.fruit |=
( index_of("apple"; .[].type) as $ia
| index_of("banana"; .[].type) as $ib
| .[$ia].count as $ac
| .[$ia].count = .[$ib].count
| .[$ib].count = $ac
)
内置的 index
(在 jq 1.6 及更早版本中)没有有效实现,虽然是用 C 编写的,但它通常很快。这是一个算法高效的实现:
def ix($x):
label $go
| foreach .[] as $v (-1; .+1; select($v == $x) | (., break $go))
// null;
答案 2 :(得分:0)
.food.fruit |= (
( .[] | select( .type == "apple" ) | .count ) as $count1 |
( .[] | select( .type == "banana" ) | .count ) as $count2 |
( .[] | select( .type == "apple" ) | .count ) = $count2 |
( .[] | select( .type == "banana" ) | .count ) = $count1
)
.food.fruit |= (
( .[] | select( .type == "apple" ) | .count ) as $count1 |
( .[] | select( .type == "banana" ) | .count ) as $count2 |
.[] |= (
.count = (
if .type == "apple" then
$count2
elif .type == "banana" then
$count1
else
.count
end
)
)
)