Reputation: 23
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
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": ""
}
}
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
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
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:
first
object.status
to the object itselfdel()
the .status
keyjoin()
on the .display
to get single value instead of the array.Output:
{
"computers": {
"host": "example",
"platform": "some_platform",
"description": "",
"working": "yes",
"display": "no"
}
}
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