jq how to merge array objects into single object - jq

in jq its possible to add objects using the + operator
if you have an array
[
{
"a": "value"
},
{
"b": "value"
},
{
"c": "value"
}
]
I want to convert it into a single object { a:"value", b:"value", c:"value" }
I can use the following filter .[0] + .[1] + .[2], but i want to do it for the whole array without specifying all the indexes.

You can use reduce:
reduce .[] as $o ({}; . + $o)
returns:
{
"a": "value",
"b": "value",
"c": "value"
}

The simplest way is just to call add filter.
"The filter add takes as input an array, and produces as output the elements of the array added together. This might mean summed, concatenated or merged depending on the types of the elements of the input array - the rules are the same as those for the + operator (described above)."
Source: https://stedolan.github.io/jq/manual/#add
$ cat test.json
[
{
"a": "value"
},
{
"b": "value"
},
{
"c": "value"
}
]
$ jq 'add' test.json
{
"a": "value",
"b": "value",
"c": "value"
}
As mentioned by peak in the comment, you can even skip wrapping add filter with the quotes:
$ jq add test.json
{
"a": "value",
"b": "value",
"c": "value"
}

In case you want to merge all objects in array recursively (didn't find any similar answer here):
jq 'reduce .[] as $x ({}; . * $x)'

Related

jq: list users belonging to a specific group in array

input json:
[
{
"user": "u1"
},
{
"user": "u2",
"groups": [
{
"id": "100001",
"name": "G1"
},
{
"id": "100002",
"name": "G2"
}
]
},
{
"user": "u3",
"groups": [
{
"id": "100001",
"name": "G1"
}
]
}
]
I want to find all users belonging to specific group (searching by group name or group id in the groups array)
$ jq -r '.[]|select(.groups[].name=="G1" | .user)' json
jq: error (at json:27): Cannot iterate over null (null)
Desired output format when searching of example group G1 would be:
u2
u3
Additional question:
Is it possible to produce comma-separated output u2,u3 without using external utilities like tr?
Better enter your serach data from parameters using --arg and use any to avoid duplicate outputs if both inputs match:
jq -r --arg id "" --arg name "G1" '
.[] | select(.groups | map(.id == $id or .name == $name) | any)? | .user
'
u2
u3
Demo
Using ? as the Optional Object Identifier-Index operator, you could do a select as below
map(select(.groups[].name == "G1")? | .user)
and un-wrap the results from the array by using [] at the end of the filter. To combine multiple selection conditions use the boolean operators with and/or inside the select statement
See demo on jqplay

How to abbreviate/truncate the value of every key?

I have a JSON like:
{
"a": "hi",
"b": 4213,
"c": 23154646789132456,
"d": "a very long string that should be shortened",
"e": {
"x": "some value",
"y": {
"alpha": "foo"
}
}
}
I would like to use jq to "abbreviate" values longer than n characters. Each value should be converted to a string s, and if the result is longer than n, it should be replaced with s[:n] + " ...". If the string is not too long, it should ideally be left as the original value. I expect a result like:
# n=5
{
"a": "hi",
"b": 4213, # "4213" would be acceptable too, but not preferable
"c": "23154 ...",
"d" : "a ver ...,
"e" : "{\n\"x\" ..." # I don't care how whitespace is handled
}
# yes, I know there's no comments in JSON :)
The idea is to have something like "folding" when looking at complex objects, so that I can get an overview of what top level keys are there, and then decide which key I want to "zoom in" to (eg jq '.e').
In Python I could do something like:
j = load_my_json()
for k in j:
s = str(j[k])
if len(s) > n:
j[k] = f"{s[:10]}..."
But how can I do it in jq?
After some trial and error, I came up with:
$ cat j.json | jq 'to_entries | map({(.key): (.value | tostring[:20] + " ...")})' | jq -s '.[] | add'
{
"a": "shortval",
"b": "4213",
"c": "23154646789132456",
"d": "a very long string t",
"e": "{\"x\":\"some value\",\"y"
}
Which is what I want. This seems a bit inelegant, especially having to do the -s call, but it's good enough for what I'm doing. I'll leave the question unanswered in case someone has a better solution.
In the simplest case, we convert each item using tostring and check the string's length
$ jq --argjson n 10 '.[] |= (
tostring | if length > $n then .[:$n] + "..." else . end
)' j.json
{
"a": "hi",
"b": "4213",
"c": "2315464678...",
"d": "a very lon...",
"e": "{\"x\":\"some..."
}
To prevent the numbers from being converted to strings, we can wrap the simple case with another if condition checking the item's type
$ jq --argjson n 10 '.[] |= (
if type == "number" then . else
tostring | if length > $n then .[:$n] + "..." else . end
end
)' j.json
{
"a": "hi",
"b": 4213,
"c": 23154646789132456,
"d": "a very lon...",
"e": "{\"x\":\"some..."
}
Finally, if we want to keep the numbers as numbers if and only if their string representation matches the length criteria, we serialize both conditions with both applying tostring once for conversion and once for testing:
$ jq --argjson n 10 '.[] |= (
if type == "number" then . else tostring end
| if tostring | length > $n then tostring | .[:$n] + "..." else . end
)' j.json
{
"a": "hi",
"b": 4213,
"c": "2315464678...",
"d": "a very lon...",
"e": "{\"x\":\"some..."
}

JQ how to combine heterogeneous objects into one array?

How could I use JQ to parse the following JSON object and produce the output below?
JSON Input:
{
"key1": {
"a": "A"
},
"key2": {
"b": "123"
},
"key3": {
"c": ["C1", "C2"]
}
}
Desired Output:
[
"a": "A",
"b": 123,
"c": "C1",
"c": "C2"
]
The following program produces the output shown below it:
def q: "\"\(.)\"";
.[]
| to_entries[]
| (.key|q) as $k
| if .value|type == "array"
then .value[] | "\($k): \(q)"
else "\($k): \(.value|q)"
end
Output:
"a": "A"
"b": "123"
"c": "C1"
"c": "C2"
This, or something very much like it, should be sufficient for using in a bash script, but if you really want the format shown in the Q, feel free to fiddle around. A more useful way to spend your time would probably be to read up on jq-bash interoperability, e.g. here on SO:
Is there a way to output jq into multiple variables for bash script?
get field from json and assign to variable in bash script?
... and many others.

Combine the value from a key with all array entries

I have json input as follows:
[{
"a": "123",
"b": [
"xyz",
"uvw"
]
}, {
"a": "456",
"b": [
"ghi"
]
}]
and I'd like to produce a list where each object's "a" is combined with each element of "b" using a delimiter. Is this possible to do using jq?
123|xyz
123|uvw
456|ghi
You can change the delimiter on the fly if you parameterize it.
$ jq -r --arg delim '|' '.[] | "\(.a)\($delim)\(.b[])"' input.json

How do I select multiple fields in jq?

My input file looks something like this:
{
"login": "dmaxfield",
"id": 7449977,
...
}
{
"login": "dmaxfield",
"id": 7449977,
...
}
I can get all the login names with this : cat members | jq '.[].login'
but I have not been able to crack the syntax to get both the login and id?
You can use jq '.[] | .login, .id' to obtain each login followed by its id.
This works for me:
> echo '{"a":1,"b":2,"c":3}{"a":1,"b":2,"c":3}' | jq '{a,b}'
{
"a": 1,
"b": 2
}
{
"a": 1,
"b": 2
}
Just provide one more example here (jq-1.6):
Walk through an array and select a field of an object element and a field of object in that object
echo '[{"id":1, "private_info": {"name": "Ivy", "age": 18}}, {"id":2, "private_info": {"name": "Tommy", "aga": 18}}]' | jq ".[] | {id: .id, name: .private_info.name}" -
{
"id": 1,
"name": "Ivy"
}
{
"id": 2,
"name": "Tommy"
}
Without the example data:
jq ".[] | {id, name: .private_info.name}" -
.[]: walk through an array
{id, name: .private_info.name}: take .id and .private_info.name and wrap it into an object with field name "id" and "name" respectively
In order to select values which are indented to different levels (i.e. both first and second level), you might use the following:
echo '[{"a":{"aa":1,"ab":2},"b":3,"c":4},{"a":{"aa":5,"ab":6},"b":7,"c":8}]' \
| jq '.[]|[.a.aa,.a.ab,.b]'
[
1,
2,
3
]
[
5,
6,
7
]

Resources