Reputation: 577
I have the following JSON array
[
{
"name": "California",
"Data": {
"AB00001": ["Los Angeles", "San Francisco", "Sacramento", "Fresno", "San Jose", "Palo Alto"]
}
},
{
"name": "Oregon",
"Data": {
"CD00002": ["Portland", "Salem", "Hillsboro"]
}
},
{
"name": "Washington",
"Data": {
"EF00003": ["Seattle", "Tacoma", "Spokane", "Bellevue"]
}
}
]
With jq '.[].Data[] | length'
I can get the length of each array, but I need to create Length
key under Data
object and assign to it the length of the array which is in the Data object. The result should look like the following:
[
{
"name": "California",
"Data": {
"ID00001": ["Los Angeles", "San Francisco", "Sacramento", "Fresno", "San Jose", "Palo Alto"],
"Length": 6
}
},
{
"name": "Oregon",
"Data": {
"ID00002": ["Portland", "Salem", "Hillsboro"],
"Length": 3
}
},
{
"name": "Washington",
"Data": {
"ID00003": ["Seattle", "Tacoma", "Spokane", "Bellevue"],
"Length": 4
}
}
]
The problem here is that in my example the object name holding the array (Data
in my example) and the array name itself (AB00001/CD00002/EF00003
) are not known in advance. However, the values of the name
key is known. Also, the array might be empty, so in this case, the Length
should be 0.
So the algorithm pseudocode should be either of one as I've envisioned:
for the whole JSON file,
if the type is an array,
then assign it to the Length key created in the parent object of that array,
next
or
For the specific value in the name key, select,
if the entry contains an array
create Length key in the array's parent object,
assign the length of the array,
select the next value of the name key
I tried to use with jq's walk
or ..
for the first approach but didn't work.
What are the alternatives?
Upvotes: 8
Views: 14042
Reputation: 116919
A solution to the immediate problem (in which .Data is a single-key object with an array-valued key) could be as simple as:
map( .Data.Length = (.Data[]|length) )
This can be adapted to the more general problem in steps. First, consider this generalization:
map( .Data |= if length==1 and (.[]|type) == "array"
then .Length = (.[]|length)
else . end )
To make the actual solution easier to understand, let's define a helper function:
def addLength:
(first(keys_unsorted[] as $k
| select(.[$k]|type == "object")
| .[$k]
| keys_unsorted[] as $l
| select(.[$l]|type == "array")
| [$k,$l]) // null) as $a
| if $a
then setpath([$a[0],"Length"]; getpath($a)|length)
else .
end;
A general solution can now be written using walk
:
walk(if type == "object" and has("name")
then addLength
else . end)
Upvotes: 10
Reputation: 31
This seems to be what you want:
jq '.[].Data |= . + { length:.[]|length } ' data.json
Output:
[
{
"name": "California",
"Data": {
"AB00001": [
"Los Angeles",
"San Francisco",
"Sacramento",
"Fresno",
"San Jose",
"Palo Alto"
],
"length": 6
}
},
{
"name": "Oregon",
"Data": {
"CD00002": [
"Portland",
"Salem",
"Hillsboro"
],
"length": 3
}
},
# etc.
Upvotes: 3