vile_goat
vile_goat

Reputation: 23

How do you replace a nested array with a flattened version of itself in jq?

Taking a json file as input such as:

{"computers":
    [{"host":"example",
    "platform":"some_platform",
    "status":
        {"working":"yes",
        "display":["no"]},
    "description":""
    }]
}

...how can this be flattened to this form:

{"computers":
    "host":"example",
    "platform":"some_platform",
    "working":"yes",
    "display":"no",
    "description":""
}

ie. the status element has been flattened, the square brackets in "display":["no"] have been removed, and the square brackets around "computers":[...] have been removed.

I have so far tried using flatten in multiple ways, eg.:

cat ./output.json | jq '.computers|.[]|.status|flatten'

but this only outputs the flattened version of the contents of the status element. I cannot work out how to replace the contents with the flattened version.

Upvotes: 0

Views: 136

Answers (3)

pmf
pmf

Reputation: 36326

You can generically "flatten" nested objects by recursively traversing to their scalars, and putting them together by taking the deepest field name available:

.computers |= ([paths(scalars) as $p | {($p | map(strings)[-1]): getpath($p)}] | add)
{
  "computers": {
    "host": "example",
    "platform": "some_platform",
    "working": "yes",
    "display": "no",
    "description": ""
  }
}

Demo

Note: You didn't define how arrays should be handled in the general case. For instance, what should happen if the .computers array or the .display array had more than just that single element. This approach merges them with latter elements overwriting previous ones if field names in the result object clash.

Upvotes: 1

knittl
knittl

Reputation: 265668

If your JSON structure is fixed, the following could do:

.computers
| first
| { host, platform, description }
+ (.status | .display |= first)
| { computers: . }

or

.computers
| first
| del(.status) + (.status | .display |= first)
| { computers: . }

or

{
    computers: (
        .computers[0] | del(.status) + (.status | .display |= first)
    )
}

Output:

{
  "computers": {
    "host": "example",
    "platform": "some_platform",
    "description": "",
    "working": "yes",
    "display": "no"
  }
}

Another alternative is reassigning the computers property, similar to 0stone0's solution, but a little bit shorter:

.computers |= (first | del(.status) + (.status | .display |= first))

Upvotes: 1

0stone0
0stone0

Reputation: 44162

If you're sure the computers array will just contain a single object, you could use:

.computers |= (first | . + .status | del(.status) | (.display |= join(.)))

That will alter the value of .computers by doing to following:

  • Get the first object
  • Move everything inside .status to the object itself
  • del() the .status key
  • join() on the .display to get single value instead of the array.

Output:

{
  "computers": {
    "host": "example",
    "platform": "some_platform",
    "description": "",
    "working": "yes",
    "display": "no"
  }
}

JqPlay Demo


If there could be multiple objects in the computer array, first will ensure the first object is used.

We could also use last to get the last one:

.computers |= (last | . + .status | del(.status) | (.display |= join(.)))

Or combine map() and add to loop over all the objects, and add them together, then the the keys will be overwritten so the last value will stay visible, this might be handy if not all the objects have all the keys:

.computers |= (map(. + .status | del(.status) | (.display |= join(.))) | add)

Upvotes: 1

Related Questions