How to swap key and value of an object using jq? - json

Using jq I would like to inverse a json object so that the property becomes the value and the value becomes the key.
Source:
{
"123": "Foobar"
"567": "Poit"
}
Goal:
{
"Foobar": "123"
"Poit": "567"
}
How can I achieve that?

In your particular case:
to_entries | map( {(.value) : .key } ) | add
More robustly:
to_entries | map( {(.value|tostring) : .key } ) | add
Or if you prefer:
with_entries( .key as $k | .key = (.value|tostring) | .value = $k )
Caveat: all these are potentially lossy.

If some keys have equal values then probably you would like to get an array of keys as value:
to_entries
| map( {(.value) : {(.key):null} } )
| reduce .[] as $item ({}; . * $item)
| to_entries
| map({key:.key, value:(.value|keys)})
| from_entries
input:
{
"key1": "val0",
"key2": "val1",
"key3": "val1"
}
output:
{
"val0": ["key1"],
"val1": ["key2", "key3"]
}

I would use an approach similar to #peak's answer but without using the add method
First use to_entries to get an output like this:
-> to_entries
Output:
[
{
"key": "123",
"value": "Foobar"
},
{
"key": "567",
"value": "Poit"
}
]
Then use map to swap the keys with values:
-> to_entries | map({key: .value|tostring, value: .key}) ###**tostring** method converts a number to string since keys can't be numbers
Output:
[
{
"key": "Foobar",
"value": "123"
},
{
"key": "Poit",
"value": "567"
}
]
Finally use from_entries to remove the key/value and return back to the original format:
-> to_entries | map({key: .value|tostring, value: .key}) | from_entries
Output:
{
"Foobar": "123",
"Poit": "567"
}

Related

Reduce nested json (PowerDNS stats)

I'm trying to improve on a jq reduce, but finding that some of the returned data is nested and the code I'm using breaks on that.
This is where I've got the jq code from: https://github.com/influxdata/telegraf/tree/master/plugins/inputs/powerdns_recursor
Taking tonumber off I get the following clipped output:
[...]
"x-ourtime8-16": "0",
"zone-disallowed-notify": "0",
"response-by-qtype": [
{
"name": "A",
"value": "8958"
},
{
"name": "NS",
"value": "6"
},
[...]
The original code, with tonumber left in:
curl -s -H 'X-API-Key: <key>' http://127.0.0.1:8082/api/v1/servers/localhost/statistics | jq 'reduce .[] as $item ({}; . + { ($item.name): ($item.value|tonumber)})'
The output I'm after:
[...]
"x-ourtime8-16": 0,
"zone-disallowed-notify": 0,
"response-by-qtype.A": 8958,
"response-by-qtype.NS": 6,
[...]
I've spent some time Googling jq and nested input, but I don't want the index numbers this gave me in the names. I'm hoping a small tweak will do the trick.
To transform this input :
{
"x-ourtime8-16": "0",
"zone-disallowed-notify": "0",
"response-by-qtype": [
{
"name": "A",
"value": "8958"
},
{
"name": "NS",
"value": "6"
}
]
}
You can run :
jq ' to_entries |
map(if (.value | type) == "string"
then .value |= tonumber
else .key as $key | .value[] |
.name |= $key+"."+. |
.value |= tonumber
end
) | from_entries
' input.json
to get :
{
"x-ourtime8-16": 0,
"zone-disallowed-notify": 0,
"response-by-qtype.A": 8958,
"response-by-qtype.NS": 6
}
You can convert numeric strings to numbers using:
if type == "string" then . as $in | try tonumber catch $in else . end
As a post-processing step, you could use walk as a wrapper:
walk(if type == "string" then . as $in | try tonumber catch $in else . end)

jq: map arrays to csv field headers

Is there a way to export a json like this:
{
"id":"2261026",
"meta":{
"versionId":"1",
"lastUpdated":"2021-11-08T15:13:39.318+01:00",
},
"address": [
"string-value1",
"string-value2"
],
"identifier":[
{
"system":"urn:oid:2.16.724.4.9.20.93",
"value":"6209"
},
{
"system":"urn:oid:2.16.724.4.9.20.2",
"value":"00042"
},
{
"system":"urn:oid:2.16.724.4.9.20.90",
"value":"UAB2"
}
]
}
{
"id":"2261027",
"meta":{
"versionId":"1",
"lastUpdated":"2021-11-08T15:13:39.318+01:00",
},
"address": [
"string-value1",
"string-value2",
"string-value3",
"string-value4"
],
"identifier":[
{
"system":"urn:oid:2.16.724.4.9.20.93",
"value":"6205"
},
{
"system":"urn:oid:2.16.724.4.9.20.2",
"value":"05041"
}
]
}
I'd like to get something like this:
"id","meta_versionId","meta_lastUpdated","address","identifier0_system","identifier0_value","identifier1_system","identifier1_value","identifier2_system","identifier2_value"
"2261026","1","2021-11-08T15:13:39.318+01:00","string-value1|string-value2","urn:oid:2.16.724.4.9.20.93","6209","urn:oid:2.16.724.4.9.20.2","00042","urn:oid:2.16.724.4.9.20.90","UAB2"
"2261027","1","2021-11-08T15:13:39.318+01:00","string-value1|string-value2|string-value3|string-value4","urn:oid:2.16.724.4.9.20.93","6205","urn:oid:2.16.724.4.9.20.2","05041",,
In short:
address array field string values has to be mapped joining its values using "|" character. Example: "string-value1|string-value2"
identifiers array field objects have to be mapped to "n-field-header". Example: "identifier0_system","identifier0_value","identifier1_system","identifier1_value","identifier2_system","identifier2_value,..."
Any ideas?
Try this
jq -r '[
.id,
(.meta | .versionId, .lastUpdated),
(.address | join("|")),
(.identifier[] | .system, .value)
] | #csv'
Demo
To prepend a header row with the number of identifierX_system and identifierX_value field pairs in it matching the length of the input's longest identifier array, try this
jq -rs '[
"id",
"meta_versionId", "meta_lastUpdated",
"address",
(
range([.[].identifier | length] | max)
| "identifier\(.)_system", "identifier\(.)_value"
)
], (.[] | [
.id,
(.meta | .versionId, .lastUpdated),
(.address | join("|")),
(.identifier[] | .system, .value)
]) | #csv'
Demo

JSON/JQ: Merge 2 files on key-value with condition

I have 2 JSON files. I would like to use jq to take the value of "capital" from File 2 and merge it with File 1 for each element where the same "name"-value pair occurs. Otherwise, the element from File 2 should not occur in the output. If there is no "name"-value pair for an element in File 1, it should have empty text for "capital."
File 1:
{
"countries":[
{
"name":"china",
"continent":"asia"
},
{
"name":"france",
"continent":"europe"
}
]
}
File 2:
{
"countries":[
{
"name":"china",
"capital":"beijing"
},
{
"name":"argentina",
"capital":"buenos aires"
}
]
}
Desired result:
{
"countries":[
{
"name":"china",
"continent":"asia",
"capital":"beijing"
},
{
"name":"france",
"continent":"europe",
"capital":""
}
]
}
You could first construct a dictionary from File2, and then perform the update, e.g. like so:
jq --argfile dict File2.json '
($dict.countries | map( {(.name): .capital}) | add) as $capitals
| .countries |= map( .capital = ($capitals[.name] // ""))
' File2.json
From a JSON-esque perspective, it would probably be better to use null for missing values; in that case, you could simplify the above by omitting // "".
Using INDEX/2
If your jq has INDEX/2, then the $capitals dictionary could be constructed using the expression:
INDEX($dict.countries[]; .name) | map_values(.capital)
Using INDEX makes the intention clearer, but if efficiency were a major concern, you'd probably be better off using reduce explicitly:
reduce $dict.countries[] as $c ({}; . + ($c | {(.name): .capital}))
One way:
$ jq --slurpfile file2 file2.json '
{ countries:
[ .countries[] |
. as $curr |
$curr + { capital: (($file2[0].countries[] | select(.name == $curr.name) | .capital) // "") }
]
}' file1.json
{
"countries": [
{
"name": "china",
"continent": "asia",
"capital": "beijing"
},
{
"name": "france",
"continent": "europe",
"capital": ""
}
]
}
An alternative:
$ jq -n '{ countries: ([inputs] | map(.countries) | flatten | group_by(.name) |
map(select(.[] | has("continent")) | add | .capital //= ""))
}' file[12].json

JQ - how to display objects based on on the value of objects in an array

I have a JSON file that looks like this:
{
"InstanceId": "i-9KwoRGF6jbhYdZi823aE4qN",
"Tags": [
{
"Key": "blah",
"Value": "server-blah"
},
{
"Key": "environment",
"Value": "ops"
},
{
"Key": "server_role",
"Value": "appserver"
},
{
"Key": "Name",
"Value": "some_name"
},
{
"Key": "product",
"Value": "some_server"
}
]
}
{
...more objects like the above...
}
I need to display the InstanceId where "Key" == "environment" and "Value" == "ops".
I have jq-1.6.
If I say:
cat source.json | jq '
{ InstanceId, Tags } |
(.Tags[] | select( .Key == "environment" ))
'
I get some of what I want, but I cannot figure out how to include InstanceId in the output nor how to incorporate the "and" part of the select.
Here is a simple but efficient approach using any:
select( any(.Tags[]; .Key=="environment" and .Value == "ops") )
| .InstanceId
An alternative approach that avoids .Tags[]:
{"Key": "environment", "Value": "ops"} as $object
| select( .Tags | index($object) )
| .InstanceId
I'm not sure if this is the exact output you're looking for (comment if it isn't), but this will output the InstanceIds of JSON objects that contain a Tag with Key environment and Value ops.
jq 'select( .Tags[] | (.Key == "environment" and .Value == "ops")) | .InstanceId' < source.json

how to use jq to merge/join/concat values with the same key

I need to aggregate values by key. Example JSON input is:
$ cat json | jq
[
{
"key": "john",
"value": "ontario"
},
{
"key": "ryan",
"value": "chicago"
},
{
"key": "ryan",
"value": "illinois"
},
{
"key": "john",
"value": "toronto"
},
]
Is it possible and if so how to merge/join/concat values with the same key so that the result is:
[
{
"key": "john",
"value": "toronto ontario"
},
{
"key": "ryan",
"value": "illinois chicago"
},
]
I am targetting JQ specifically because of its ease of use from cfengine.
Group the pairs by key, then combine the values.
group_by(.key) | map({key:.[0].key,value:(map(.value) | join(" "))})
For this type of problem, I prefer to avoid the overhead of sorting, and to guarantee that the ordering of the objects in the input is respected.
Here's one approach that assumes the values associated with "key" and "value" are all strings (as is the case in the example). This assumption makes it easy to avoid an inefficient lookup:
def merge_by_key(separator):
reduce .[] as $o
({}; $o["key"] as $k
| if .[$k] then .[$k] += (separator + $o["value"])
else .[$k] = $o["value"] end);
merge_by_key(" ") | to_entries
Output:
[{"key":"john","value":"ontario toronto"},
{"key":"ryan","value":"chicago illinois"}]
Generic solution
def merge_at_key(separator):
reduce .[] as $o
([];
$o["key"] as $k
| (map(.key) | index($k)) as $i
| if $i then (.[$i] | .value) += (separator + $o["value"])
else . + [$o] end);