I would like to check for the presence of a subkey (or for its type) in a JSON file using jq without filtering the data. I need this to obtain a list of all entities from the following JSON file, with the first pair of coordinates if more than one pair is available. The problem is the partly nested structure: The "location" object is only available for some entries, and the "coordinates" element is an array only if multiple locations are available.
{"records":[
{
"id": 1,
"location": {
"coordinates": {
"lat": 42,
"lon": -71
}
}
},
{
"id": 2,
"location": {
"coordinates": [
{
"lat": 40,
"lon": -73
},
{
"lat": 39,
"lon": -75
}
]
}
},
{
"id": 3,
"location": null
}]}
So I tried the "has" function, which does not seem to work for subkeys. I imagined something like this:
cat file.json | jq '.records[] | if has("location.coordinates") then [do something] else [do something else] end'
Is there any way to check for subkeys? As I need to maintain all entries in the dataset, filtering via "select" etc. does not seem to be an option.
To clarify my question: I hoped to get a JSON output similar to this (but I would be happy to handle other formats):
{"records":[
{"id": 1, "lat", xx, "lon": xx}
{"id": 2, "lat", yy, "lon": yy}
{"id": 3, "lat", null, "lon": null}
]}
CodePudding user response:
Instead off has
, you can use a select
with an |=
opertator:
.records |= map(.id as $id | (.location.coordinates | (if type == "array" then .[0] else . end) as $q | ({ $id, lat: null, lon: null } $q) ))
This generates:
{
"records": [
{
"id": 1,
"lat": 42,
"lon": -71
},
{
"id": 2,
"lat": 40,
"lon": -73
},
{
"id": 3,
"lat": null,
"lon": null
}
]
}
As you can try in this online demo.
So, the above explained:
- Loop over
records
and update them
.records |= map()
- Save the id
.id as $id
- Continue on the coordinates
.location.coordinates |
- Check for an array, if so, get the fist object, otherwise, just keep it, save as
$q
(if type == "array" then .[0] else . end) as $q
- Create the final object, start with an object with just the
id
andlat
andlon
set tonull
, then we combine$q
to get the actual values from the coordinates
({ $id, lat: null, lon: null } $q)
This could most probably be simplified, so waiting for another answer that has the same idea, but more optimised.
CodePudding user response:
You can also use alternative operator //
:
jq '.records[] |= {id} ((.location.coordinates? |
if type == "array" then .[0] else . end
) // {}
)' input.json
CodePudding user response:
You could use the error suppression operator ?
in combination with the alterative operator //
to fall back to another case if the previous one fails:
.records[] |= (.location.coordinates | .[0]? // .) as {$lat,$lon} | {id,$lat,$lon}
{
"records": [
{
"id": 1,
"lat": 42,
"lon": -71
},
{
"id": 2,
"lat": 40,
"lon": -73
},
{
"id": 3,
"lat": null,
"lon": null
}
]
}