jq update list elements based on values - json

Is it possible to use jq to turn the following JSON data
[
{
"a": null,
"b": [
{
"c": "cc",
"d": "dd1"
},
{
"c": "cc",
"d": "dd1",
"e": "ee",
"f": "ff"
}
]
},
{
"b": [
{
"c": "cc",
"d": "dd2",
"e": "ee",
"f": "ff"
}
]
}
]
into
[
{
"a": null,
"b": [
[
"cc", "d1"
],
[
"cc", "d1", "ff"
]
]
},
{
"b": [
[
"cc", "d2", "ff"
]
]
}
]
?
Note that the purpose is to reduce the b list with certain elements of its items based on a condition. The condition assigns the string d1 if the value of d is dd1, otherwise d2 is assigned if dd2 is present.
The following unsuccessful attempt demonstrates the idea:
$ jq -r '.[].b[] = [.[].b[].c, ?, .[].b[].f?]'

This is probably not the best way to do what you want, but it does get the desired output and it has the conditional you asked about.
jq '.[].b |= map(
[ .c,
(if .d == "dd1" then "d1" elif .d == "dd2" then "d2" else . end),
.f // empty
] )'

Without changing the value of the d key, you would use this jq filter:
jq '.[].b[]|=(to_entries|map(.value))' file
That updates the b array into all values of the inner objects.
If you want to update the d, you could use this filter:
jq '.[].b[] |= (to_entries|
map(
if(.key=="d") then
.value|=sub("d+";"d")
else .
end
|.value)
)' file
that adds a check if the key is d the associated value is updated to remove the duplicated d character from the string. This requires jq to support for regex (to be able to use the sub function).

Related

How to update the value of the item in an array if it exists in json using jq?

I have a Json file which I want to update using jq. I am working on Ubuntu with jq-1.6
test_data.json
{
"A": "12",
"B": "34",
"C": [
["X", "test1"],
["Y", "test2"],
["Z", "test3"]
]
}
Now I want to update array C with new key:value pair. But, if the any of the key already exists then it's value should be updated.
update='[
["Z", "test4"],
["D", "test5"],
["E", "test6"]
]'
In this case the item Z already exists in test_data.json but update has new value for the item.
Expected output:
{
"A": "12",
"B": "34",
"C": [
["X", "test1"],
["Y", "test2"],
["Z", "test4"],
["D", "test5"],
["E", "test6"]
]
}
So far, I could do
cat test_data.json | jq --argjson val "${update}" '.C += $val')
But this is not updating value for item Z, instead adding new entry.
Can anyone please let me know how to resolve this?
Thanks in advance.
The .C array and the $update array both have arrays as items. You need to consider their first item to be a unique key, so that clashes can lead to overwrites. One way could be turning them into an INDEX object first, then add up those, and retrieve their items back into an array:
jq --argjson val "$update" '.C |= [[., $val | INDEX(.[0])] | add[]]' test_data.json
{
"A": "12",
"B": "34",
"C": [
[
"X",
"test1"
],
[
"Y",
"test2"
],
[
"Z",
"test4"
],
[
"D",
"test5"
],
[
"E",
"test6"
]
]
}

jq: how to replace keys with values ​from other keys whose ​are in strings of some array

Consider following array:
{
"A": 100,
"B": 200,
"C": "ccc",
"arr": [
{
"result": ".R1.R3",
"fmt": "%s::%s::baz",
"vals": [".A", ".B"]
},
{
"result": ".R2.R4",
"fmt": "%s/%s",
"vals": [".A", ".C"]
}
]
}
I need to replace keys according some format with values ​​from other keys whose ​​are in strings of some array.
Desired output:
{
"A": 100,
"B": 200,
"C": "ccc",
"R1": {"R3": "100::200::baz"},
"R2": {"R4": "100/ccc"}
}
You didn't specify what language you use in the .vals array items to reference into the document. If you were trying to execute (arbitrary) jq code from there, know that jq cannot do that with code provided as a string value. jq also doesn't provide printf-style substitutions using %s (and others). Therefore, you either need to re-implement a whole bunch of (third-party) functionality, or revert to a simpler scheme describing your references and substitutions.
For the sake of simplicity, this solution just removes the first character (the dot) from the .vals array items and treats the result as top-level field name, and then simply replaces each occurrence of a literal %s with the next value. This should give you an overview of the general technique.
. as $top | reduce .arr[] as $a (del(.arr); .[$a.result] = (
reduce $a.vals[][1:] as $val ($a.fmt; sub("%s"; $top[$val] | #text))
))
{
"A": 100,
"B": 200,
"C": "ccc",
".R1": "100::200::baz",
".R2": "100/ccc"
}
Demo
One quite simple way of improving the reference language is to instead use path expressions jq provides functions for. They are represented as arrays with field names as strings items and array indices as number items. .A would become ["A"], .A[3].B would become ["A",3,"B"], and so on. Thus, assume your input looked like this:
{
"A": 100,
"B": 200,
"C": "ccc",
"arr": [
{
"result": ".R1",
"fmt": "%s::%s::baz",
"vals": [["A"], ["B"]]
},
{
"result": ".R2",
"fmt": "%s/%s",
"vals": [["A"], ["C"]]
}
]
}
Then you could use getpath to evaluate the given path expressions as above:
. as $top | reduce .arr[] as $a (del(.arr); .[$a.result] = (
reduce $a.vals[] as $path ($a.fmt; sub("%s"; $top | getpath($path) | #text))
))
{
"A": 100,
"B": 200,
"C": "ccc",
".R1": "100::200::baz",
".R2": "100/ccc"
}
Demo
Edit: As the question has been modified with the .result value now also being subject to reference interpretation, measures taken for .vals therefore apply to it as well. This implies changing the suggested source document format to use path expressions as in "result": ["R1", "R3"], and changing the assignment in the suggested code from .[$a.result] = ... to setpath($a.result; ...):
{
"A": 100,
"B": 200,
"C": "ccc",
"arr": [
{
"result": ["R1", "R3"],
"fmt": "%s::%s::baz",
"vals": [["A"], ["B"]]
},
{
"result": ["R2", "R4"],
"fmt": "%s/%s",
"vals": [["A"], ["C"]]
}
]
}
. as $top | reduce .arr[] as $a (del(.arr); setpath($a.result;
reduce $a.vals[] as $path ($a.fmt; sub("%s"; $top | getpath($path) | #text))
))
{
"A": 100,
"B": 200,
"C": "ccc",
"R1": {
"R3": "100::200::baz"
},
"R2": {
"R4": "100/ccc"
}
}
Demo

How to insert a field at the top of an object?

This is my code
element='{"x":"zero"}'
example='[
{
"a":"one",
"b":"two",
"c":"three"
},
{
"d":"four",
"e":"five",
"f":"six"
}]'
jq --argjson el "$element" '.[] += $el' <<< $example
It currently outputs
[
{
"a":"one",
"b":"two",
"c":"three",
"x":"zero"
},
{
"d":"four",
"e":"five",
"f":"six",
"x":"zero"
}
]
But i would like to have the "x":"zero" be the first element of all array elements, not the last.
Inverting the variable with the .[] iterator would make no sense. Any idea how this can be done?
In order for the new field to appear at the top, the object that holds it must appear first in the addition statement. For example:
$ jq --argjson el "$element" 'map($el + .)' <<< $example
[
{
"x": "zero",
"a": "one",
"b": "two",
"c": "three"
},
{
"x": "zero",
"d": "four",
"e": "five",
"f": "six"
}
]

select all keys with given value

With jq, how do I select all objects, which may be nested, with a desired value?
For example, given the following:
{
"a": "b",
"c": {
"d": {
"e": "f",
"z": "b"
}
}
}
How do I filter down to objects whose value is "b"?
{
"a": "b",
"c": {
"d": {
"z": "b"
}
}
}
Conversely, how do I select objects whose value is not "b"?
{
"c": {
"d": {
"e": "f"
}
}
}
I've attempted at the problem by using the select and walk functions, but could not get exactly what I wanted.
For the first question:
reduce paths(. == "b") as $p ({}; setpath($p; "b"))
For the second:
reduce paths(. == "b") as $p (.; delpaths([$p]))
or even more succinctly:
delpaths( [ paths(. == "b") ] )
Furthermore
The first question can also be answered without reduce, but to get it right generically requires more verbosity, e.g.:
delpaths( [ paths( (. !="b") and (type|IN("object","array") | not) ) ] )

jq recursively update values for certain elements

The intent for the JSON data below is to update the value of the field dst with the value of src within all elements of type t, regardless of depth within the tree, while at the same time preserving the whole structure of the data.
Is this possible with jq? My several attempts have boiled down to the following command that is not working to achieve the intended purpose:
$ jq -r 'map_values(select(.. | .type? == "t" |= (.dst = .src)))'
{
"a": "b",
"c": [
{
"type": "t",
"src": "xx",
"dst": "zz"
},
{
"type": "t",
"src": "xx",
"dst": "zz"
}
],
"d": [
{
"e": [
{
"type": "t",
"src": "xx",
"dst": "zz"
}
]
},
{
"type": "t2",
"src": "xx",
"dst": "zz"
}
]
}
Is this possible with jq?
jq is Turing-complete :-)
Here's a simple solution:
walk( if type == "object" and .type == "t" then .dst = .src else . end)
If your jq does not have walk/1, then it might be a good time to upgrade (to jq 1.6); otherwise, you can snarf its def from the web, e.g. by googling: jq "def walk"
Alternatively ...
reduce paths as $x (.;
if (getpath($x)|.type? // false) == "t"
then setpath( $x + ["dst"]; getpath( $x + ["src"] ))
else . end)