简体   繁体   中英

Jq - calculate length of each array in JSON and update it

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' 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?

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 )

Solution

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)

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM