Reputation: 13
I want to use jq to move a nested key:value pair up one level. So given a geojson array of objects like this:
{
"type" : "FeatureCollection",
"features" : [ {
"type" : "Feature",
"geometry" : {
"type" : "MultiLineString",
"coordinates" : [ [ [ -74, 40 ], [ -73, 40 ] ] ]
},
"properties" : {
"startTime" : "20160123T162547-0500",
"endTime" : "20160123T164227-0500",
"activities" : [ {
"activity" : "car",
"group" : "car"
} ]
}
} ]
}
I want to return the exact same object, but with "group": "car"
in the features
object. So the result would look something like this:
{
"type" : "FeatureCollection",
"features" : [ {
"type" : "Feature",
"geometry" : {
"type" : "MultiLineString",
"coordinates" : [ [ [ -74, 40 ], [ -73, 40 ] ] ]
},
"properties" : {
"type" : "move",
"startTime" : "20160123T162547-0500",
"endTime" : "20160123T164227-0500",
"group" : "car",
"activities" : [ {
"activity" : "car"
} ]
}
} ]
}
This seems simple, but somehow I'm struggling to figure out how to do it with jq. Help appreciated!
Upvotes: 1
Views: 4044
Reputation: 116919
Here is a generalized solution:
# move the key/value specified by apath up to the containing JSON object:
def up(apath):
def trim:
if .[-1] | type == "number" then .[0:-2] | trim
else .
end;
. as $in
| (null | path(apath)) as $p
| ($p|last) as $last
| $in
| getpath($p) as $v
| setpath(($p[0:-1]|trim) + [$last]; $v)
| del(apath)
;
With this definition, the solution is simply:
up( .features[0].properties.activities[0].group )
Upvotes: 0
Reputation: 14715
The problem doesn't discuss what should be done if there are no activities or if there is more than one activity so the following filter encapsulates the property change to a function:
def update_activity:
if .activities|length<1 then .
else
.group = .activities[0].group
| del(.activities[0].group)
end
;
.features[].properties |= update_activity
.properties
is left unmodified when there are no activities otherwise the group
of the first activity is moved to the property, leaving other activities unmodified.
So if the sample input (slightly abbreviated) were instead
{
"type" : "FeatureCollection",
"features" : [ {
"properties" : {
"activities" : [ {
"activity" : "car",
"group" : "car"
}, {
"activity" : "bike",
"group" : "bike"
} ]
}
} ]
}
the result would be
{
"type": "FeatureCollection",
"features" : [ {
"properties" : {
"group": "car",
"activities": [ {
"activity": "car"
}, {
"activity": "bike",
"group": "bike"
} ]
}
} ]
}
This approach offers a specific place to put the logic dealing with other
variations. E.g. this version of update_activity removes the .group
of
all activities:
def update_activity:
if .activities|length<1 then .
else
.group = .activities[0].group
| del(.activities[].group)
end
;
and this version also assigns .group
to null in the event there are no activites:
def update_activity:
if .activities|length<1 then
.group = null
else
.group = .activities[0].group
| del(.activities[].group)
end
;
Upvotes: 0
Reputation: 116919
In two steps (first add, then delete):
.features[0].properties |= (.group = .activities[0].group)
| del(.features[0].properties.activities[0].group)
Or still more succinctly:
.features[0].properties |=
((.group = .activities[0].group) | del(.activities[0].group))
Upvotes: 0
Reputation: 92884
jq solution:
jq '(.features[0].properties.group = .features[0].properties.activities[0].group)
| del(.features[0].properties.activities[0].group)' input.json
The output:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "MultiLineString",
"coordinates": [
[
[
-74,
40
],
[
-73,
40
]
]
]
},
"properties": {
"startTime": "20160123T162547-0500",
"endTime": "20160123T164227-0500",
"activities": [
{
"activity": "car"
}
],
"group": "car"
}
}
]
}
Upvotes: 2