Jq — вычислить длину каждого массива в JSON и обновить его

#arrays #json #select #jq

#массивы #json #выберите #jq

Вопрос:

У меня есть следующий массив JSON

 [
  {
    "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"]
    }
  }
]
  

С jq '.[].Data[] | length' помощью я могу получить длину каждого массива, но мне нужно создать Length ключ под Data объектом и присвоить ему длину массива, который находится в объекте данных. Результат должен выглядеть следующим образом:

 [
    {
        "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
        }
    }
]
  

Проблема здесь в том, что в моем примере имя объекта, содержащего массив ( Data в моем примере), и само имя массива ( AB00001/CD00002/EF00003 ) заранее неизвестны. Однако значения name ключа известны. Кроме того, массив может быть пустым, поэтому в этом случае значение Length должно быть равно 0.

Таким образом, псевдокод алгоритма должен быть одним из одного, как я предполагал:

 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
  

или

 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
  

Я пытался использовать с jq walk или .. для первого подхода, но не сработало.
Каковы альтернативы?

Ответ №1:

Решение непосредственной проблемы (в которой .Данные — это объект с одним ключом с ключом, равным массиву) может быть таким же простым, как:

 map( .Data.Length = (.Data[]|length) )
  

Это может быть адаптировано к более общей задаче поэтапно. Сначала рассмотрим это обобщение:

 map( .Data |= if length==1 and (.[]|type) == "array" 
              then .Length = (.[]|length) 
              else . end )
  

Решение

Чтобы упростить понимание фактического решения, давайте определим вспомогательную функцию:

 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;
  

Теперь общее решение может быть написано с использованием walk :

 walk(if type == "object" and has("name")
     then addLength
     else . end)
  

Комментарии:

1. спасибо, ваши ответы, как обычно, великолепны. Только один уточняющий вопрос — как можно использовать ваше решение, если я не знаю имени data ? Что дано, так это то, что для каждой {} конструкции в файле JSON есть ключ с именем name , и в том же блоке есть массив объектов, содержащий массив. Имя объекта, содержащего массив, и имя массива заранее неизвестны.

2. См. Обновление. Я надеялся, что вы сможете сделать последний шаг самостоятельно 🙂

Ответ №2:

Похоже, это то, что вы хотите:

 jq '.[].Data |= .   { length:.[]|length } ' data.json
  

Вывод:

 [
  {
    "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.
  

Комментарии:

1. Спасибо! Один вопрос — как можно обобщить ваше элегантное решение, если имя объекта, который содержит массив — Data в данном случае — неизвестно?