Gio O
Gio O

Reputation: 13

Moving a json nested key-value pair up one level with jq

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

Answers (4)

peak
peak

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

jq170727
jq170727

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

peak
peak

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

RomanPerekhrest
RomanPerekhrest

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

Related Questions