Разбор нескольких ключей / значений в дереве json с помощью jq

Используя jq, я бы хотел выбрать пары ключ / значение из следующего json:

{
  "project": "Project X",
  "description": "This is a description of Project X",
  "nodes": [
    {
      "name": "server001",
      "detail001": "foo",
      "detail002": "bar",
      "networks": [
        {
          "net_tier": "network_tier_001",
          "ip_address": "10.1.1.10",
          "gateway": "10.1.1.1",
          "subnet_mask": "255.255.255.0",
          "mac_address": "00:11:22:aa:bb:cc"
        }
      ],
      "hardware": {
        "vcpu": 1,
        "mem": 1024,
        "disks": [
          {
            "disk001": 40,
            "detail001": "foo"
          },
          {
            "disk002": 20,
            "detail001": "bar"
          }
        ]
      },
      "os": "debian8",
      "geo": {
        "region": "001",
        "country": "Sweden",
        "datacentre": "Malmo"
      },
      "detail003": "baz"
    }
  ],
  "detail001": "foo"
}

Для примера я хотел бы проанализировать следующие ключи и их значения: "Project", "name", "net_tier", "vcpu", "mem", "disk001", "disk002".

Я могу анализировать отдельные элементы без особых проблем, но из-за иерархической природы полного анализа мне не сильно повезло при разборе различных ветвей (т. Е. Как сетей, так и аппаратных> дисков).

Любая помощь приветствуется.

Редактировать:

Для ясности вывод, который я собираюсь сделать, это разделенный запятыми CSV. С точки зрения синтаксического анализа всех комбинаций, охват образца данных в примере пока подойдет. Надеюсь, я смогу расширить любые предложения.

3 ответа

Решение

Вот другой фильтр, который вычисляет уникальный набор сетевых имен и имен дисков, а затем генерирует результат со столбцами, соответствующими данным.

  {
    tiers: [ .nodes[].networks[].net_tier ] | unique
  , disks: [ .nodes[].hardware.disks[] | keys[] | select(startswith("disk")) ] | unique
  } as $n

| def column_names($n): [ "project", "name" ] + $n.tiers + ["vcpu", "mem"] + $n.disks ;
  def tiers($n):        [ $n.tiers[] as $t | .networks[] | if .net_tier==$t then $t else null end ] ;
  def disks($n):        [ $n.disks[] as $d | map(select(.[$d]!=null)|.[$d])[0] ] ;
  def rows($n):
      .project as $project
    | .nodes[]
    | .name as $name
    | tiers($n) as $tier_values
    | .hardware
    | .vcpu as $vcpu
    | .mem as $mem
    | .disks
    | disks($n) as $disk_values
    | [$project, $name] + $tier_values + [$vcpu, $mem] + $disk_values
  ;
  column_names($n), rows($n)

| @csv

Преимущество этого подхода становится очевидным, если мы добавим еще один узел к образцу данных:

{
  "name": "server002",
  "networks": [
    {
      "net_tier": "network_tier_002"
    }
  ],
  "hardware": {
    "vcpu": 1,
    "mem": 1024,
    "disks": [
      {
        "disk002": 40,
        "detail001": "foo"
      }
    ]
  }
}

Пробный прогон (при условии фильтрации в filter.jq и исправленные данные в data.json)

$ jq -Mr -f filter.jq data.json
"project","name","network_tier_001","network_tier_002","vcpu","mem","disk001","disk002"
"Project X","server001","network_tier_001","",1,1024,40,20
"Project X","server002",,"network_tier_002",1,1024,,40

Попробуйте онлайн!

Вот еще один подход, достаточно короткий, чтобы говорить сам за себя:

def s(f): first(.. | f? // empty) // null;

[s(.project), s(.name), s(.net_tier), s(.vcpu), s(.mem), s(.disk001), s(.disk002)]
| @csv

Призвание:

$ jq -r -f value-pairs.jq input.json

Результат:

"Project X","server001","network_tier_001",1,1024,40,20

С заголовками

Используя то же самое s/1 как указано выше:

. as $d
| ["project", "name", "net_tier", "vcpu", "mem", "disk001","disk002"]
| (., map( . as $v | $d | s(.[$v])))
| @csv

С несколькими узлами

Опять с s/1 как указано выше:

.project as $p
| ["project", "name", "net_tier", "vcpu", "mem", "disk001","disk002"] as $h
| ($h,
   (.nodes[] as $d
   | $h
   | map( . as $v | $d | s(.[$v]) )
   | .[0] = $p)
   ) | @csv

Вывод с иллюстративными многоузловыми данными:

"project","name","net_tier","vcpu","mem","disk001","disk002"
"Project X","server001","network_tier_001",1,1024,40,20
"Project X","server002","network_tier_002",1,1024,,40

Вот один из способов добиться желаемого результата.

program.jq:

["project","name","net_tier","vcpu","mem","disk001","disk002"],
  [.project]
+ (.nodes[] | .networks[] as $n |
    [
      .name,
      $n.net_tier,
      (.hardware |
        .vcpu,
        .mem,
        (.disks | add["disk001","disk002"])
      )
    ]
  )
| @csv
$ jq -r -f program.jq input.json
"project","name","net_tier","vcpu","mem","disk001","disk002"
"Project X","server001","network_tier_001",1,1024,40,20

По сути, вы хотите проецировать поля, которые вы хотите, в массивы, чтобы вы могли конвертировать эти массивы в строки CSV. Ваш ввод создает впечатление, что потенциально может быть несколько сетей для данного узла. Так что, если вы хотите вывести все комбинации, это должно быть выровнено.

Другие вопросы по тегам