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

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

Related

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

JQ - Join nested arrays and filter

I'm trying to use JQ to create the following paths:
/staging-data-0/cassandra/cassandra-client-port
/staging-data-0/cassandra/cassandra-gossip-port
from the following blob of JSON (I've stripped unnecessary bits out):
{
"DebugConfig": {
"ServerPort": 8300,
"Services": [
{
"Checks": [
{
"CheckID": "cassandra-client-port",
"Timeout": "1s"
},
{
"CheckID": "cassandra-gossip-port",
"Timeout": "1s"
}
],
"Name": "cassandra"
},
{
"Checks": [
{
"CheckID": "cockroachdb-tcp",
"Timeout": "1s"
}
],
"Name": "cockroachdb"
}
]
},
"Member": {
"Name": "staging-data-0"
},
"Meta": {
"consul-network-segment": ""
}
}
I'm struggling with the JQ manual to generate the paths, I can only pull out the last part so far with
jq '.DebugConfig.Services | map(select(.Name=="cassandra")) | map(.Checks[].CheckID)'
The final path should be /{.Member.Name}/{.DebugConfig.Services.Name}/{.DebugConfig.Services.Checks.CheckID}
Only cassandra
jq -r '{a:.Member.Name, b:.DebugConfig.Services[]} | select(.b.Name=="cassandra") | {a:.a, b:.b.Name, c:.b.Checks[].CheckID} | [.a, .b, .c] | join("/")'
staging-data-0/cassandra/cassandra-client-port
staging-data-0/cassandra/cassandra-gossip-port
Both
jq -r '{a:.Member.Name, b:.DebugConfig.Services[]} | {a:.a, b:.b.Name, c:.b.Checks[].CheckID} | [.a, .b, .c] | join("/")'
staging-data-0/cassandra/cassandra-client-port
staging-data-0/cassandra/cassandra-gossip-port
staging-data-0/cockroachdb/cockroachdb-tcp
With your input, the jq filter:
.DebugConfig.Services[] as $s
| "/\(.Member.Name)/\($s.Name)/\($s.Checks[].CheckID)"
produces:
"/staging-data-0/cassandra/cassandra-client-port"
"/staging-data-0/cassandra/cassandra-gossip-port"
"/staging-data-0/cockroachdb/cockroachdb-tcp"
Since you only want the "cassandra" strings, you just need to interject a "select" filter:
.DebugConfig.Services[] as $s
| "/\(.Member.Name)/\($s.Name)/" +
($s
| select(.Name == "cassandra")
| .Checks[].CheckID)
but it's worth noting how easy it is to process all the "Checks" items.

Filter object or array

I would like to list all the Ids and roles in a given json but where there is only a single role, rather than an array of 1 it provides it as an object, so if I run "[]?" I get the error Cannot index string with string "Name".
Extract (example.json):
{
"Person": [
{
"Roles": {
"Role": {
"#Id": "1",
"Name": "Job1"
}
}
},
{
"Roles": {
"Role": [
{
"#Id": "2",
"Name": "Job2"
},
{
"#Id": "3",
"Name": "Job3"
}
]
}
}
]
}
I hoped this may work:
jq -r . | '.Roles.Role[]?>.#Id + "," + .Roles.Role[]?>.Name'
This is the output I'd like (so I can pipe to a csv)
1,Job1
2,Job2
3,Job3
The following produces the CSV shown below. It would be easy to tweak the program to remove the double-quotation marks, etc.
.Person[]
| .Roles.Role
| if type == "array" then .[] else . end
| [.["#Id"], .Name]
| #csv
Output
"1","Job1"
"2","Job2"
"3","Job3"
Adding the index in .Person
.Person
| range(0; length) as $ix
| .[$ix]
| .Roles.Role
| if type == "array" then .[] else . end
| [$ix, .["#Id"], .Name]
| #csv

Get parent element id while parsing json data with jq

I want to print ID of parent element when child element value is client_release from JSON data.
if
data.properties.value== "client_release"
then output should be
abcd1g2f,hirk5d7b3l
I tried below, but no luck
jq '.data[].properties[]|select(.value=="client_release")|.id'
JSON data is below:
{
"data":[
{
"id":"abcd1g2f",
"resourceURI":"https://somerepo.com/service/local/privileges/abcd1g2f",
"name":"release1",
"description":"release1",
"type":"target",
"userManaged":true,
"properties":[
{
"key":"repositoryGroupId",
"value":""
},
{
"key":"method",
"value":"create,read"
},
{
"key":"repositoryId",
"value":"client_release"
},
{
"key":"repositoryTargetId",
"value":"1"
}
]
},
{
"id":"asdf1k4g",
"resourceURI":"https://somerepo.com/service/local/privileges/asdf1k4g",
"name":"release2",
"description":"release2",
"type":"target",
"userManaged":true,
"properties":[
{
"key":"repositoryGroupId",
"value":""
},
{
"key":"method",
"value":"read"
},
{
"key":"repositoryId",
"value":"formal_release"
},
{
"key":"repositoryTargetId",
"value":"1"
}
]
},
{
"id":"hirk5d7b3l",
"resourceURI":"https://somerepo.com/service/local/privileges/hirk5d7b3l",
"name":"release3",
"description":"release3",
"type":"target",
"userManaged":true,
"properties":[
{
"key":"repositoryGroupId",
"value":""
},
{
"key":"method",
"value":"create,read"
},
{
"key":"repositoryId",
"value":"client_release"
},
{
"key":"repositoryTargetId",
"value":"1"
}
]
}
]
}
The idea is right, but the data[] array should be outside the select statement,
jq '.data[] | select(.properties[].value == "client_release") | .id'
To put it in the CSV format as indicated in the question, put the result into an array and use the #csv construct
jq --raw-output '[.data[] | select(.properties[].value == "client_release") | .id] | #csv'
The following filter avoids duplications and might be more efficient than using select(.properties[].value ...):
.data
| map(select(.properties | any(.[]; .value == "client_release")) | .id)
| join(",")
(You could alternatively use #csv at the end if you want the values of .id as JSON strings.)
"repositoryId"
If attention should only be paid to the value corresponding to "repositoryId", then you could
use from_entries, e.g.:
.data
| map(select(.properties | from_entries.repositoryId == "client_release") | .id)
| join(",")

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

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"
}