Reputation: 2463
Not quite sure how to ask the question clearly but, given a recursive structure as below. How would I use walk
to match 2 or more key strings to values. I will not know where in the structure the result will be. It could be the top level or 10 levels deep.
"children": {
"ccc": [{
"id": "ddd",
"des": "object d",
"parent": "ccc",
"other": "zzz"
},{
"id": "zzz",
"des": "object z",
"parent": "ccc",
"other" : "ddd"
}]
}
I would like to find a record where key=id=ddd
&& key=parent=ccc
I would then like to add a new key/value to that record. Using .key|match("")
will give me a match to the value of the key but not the key name itself. So searching for ddd
may match both id
and other
.
I have tried several combos and if doing in bash it would look something like
match_criteria
((.key|match("id") and (.key|test("ddd"))
and
((.key|match("parent") and (.key|test("ccc"))
new_key_value
+= {"newkey":"newValue"}
insert match statement into
walk(if type == "object"
then
with_entries(if ..match_criteria.. )
then ..new_key_value.. else . end)
so the result should look like
"children": {
"ccc": [{
"id": "ddd",
"des": "object d",
"parent": "ccc",
"other": "zzz",
"newkey": "newValue"
},{
"id": "zzz",
"des": "object z",
"parent": "ccc",
"other":"ddd"
}]
}
UPDATE based on feedback in the answer from @peak i have updated the code as follows
jsonOut=$(jq 'walk(when(type == "object";
with_entries(
when(any(.value[]; .id == "ddd");
.value[] += {"newkey": "newValue"}
))))' <<< ${jsonIn})
unfortunately this still leaves two open issues
a) this code adds {"newkey": "newValue"}
to all children where the search criteria is true, ie: to both id:ddd
&& id:zzz
, rather than to just the id:ddd
record
"children": {
"ccc": [{
"id": "ddd",
"des": "object d",
"parent": "ccc",
"other": "zzz",
"newkey": "newValue"
},{
"id": "zzz",
"des": "object z",
"parent": "ccc",
"other":"ddd",
"newkey": "newValue"
}]
}
b) adding multiple section criteria to the any
clause. I have tried using the AND
or |
joining methods but this throws errors.
when(any(.value[]; .id == "ddd" | .other == "zzz"); //no match, no value added
or
when((any(.value[]; .id == "ddd") AND (any(.value[]; .other == "zzz"));
//error : unexpected ')', expecting $end
or
when(any(.value[]; .id == "ddd", .other == "zzz"); //no match, no value added
Can you advise the syntax for both issues.
UPDATE2 Understanding the when
filter a littler better, I have now nested these and it seems to work in narrowing the result set. However problem a)
updating both records when a match is true still exists.
jsonOut=$(jq 'walk(when(type == "object";
with_entries(
when(any(.value[]; .id == "ddd");
when(any(.value[]; .other == "zzz");
.value[] += {"newkey": "newValue"}
)))))' <<< ${jsonIn})
jsonIn
{"children": {
"ccc": [{
"id": "ddd",
"des": "object d",
"parent": "ccc",
"other": "zzz"
},{
"id": "zzz",
"des": "object z",
"parent": "ccc",
"other":"ddd"
}],
"www": [{
"id": "ddd",
"des": "object d",
"parent": "www",
"other": "ppp"
},{
"id": "kkk",
"des": "object z",
"parent": "www",
"other":"ddd"
}]
}}
jsonOut
{
"children": {
"ccc": [{
"id": "ddd",
"des": "object d",
"parent": "ccc",
"other": "zzz",
"newkey": "newValue"
}, {
"id": "zzz",
"des": "object z",
"parent": "ccc",
"other": "ddd",
"newkey": "newValue" <=need to NOT add this entry
}],
"www": [{
"id": "ddd",
"des": "object d",
"parent": "www",
"other": "ppp"
}, {
"id": "kkk",
"des": "object z",
"parent": "www",
"other": "ddd"
}]
}
}
Upvotes: 1
Views: 316
Reputation: 116957
Here is a response to the "UPDATED" question:
walk(when(type == "object";
with_entries(when(.key|test("ccc");
.value |= map( when(.id=="ddd";
. + {"newkey": "newValue"}))))))
In future, please follow the mcve guidelines: http://stackoverflow.com/help/mcve
Upvotes: 1
Reputation: 116957
The simplest way to use walk
here is to include the update in the "then" part of the if ... then ... else ...end
. To emphasize and clarify this, and to shorten the solution, I'll use the generic helper function, when
:
def when(filter; action): if (filter?) // null then action else . end;
The solution to the problem can now be written in a very straightforward way:
walk(when(type == "object";
with_entries(when(.key|test("ccc");
when(any(.value[]; .id == "ddd");
.value += ["ADDITIONAL"])))))
Of course you might want a fancier test than .id == "ddd", and you might want to perform the update only once per "ccc" object, but the same structure would be used.
In reality, you might also want to wrap the above expression in a def
so it can more readily be parameterized and maintained.
Upvotes: 0