Как я могу фильтровать по нескольким идентификаторам объектов, используя эквивалентные символы или чередование в JQ?

Резюме

У меня есть работающий фильтр JQ, который правильно анализирует три различных объекта имени и полезной нагрузки и массирует их в желаемый выходной формат. Проблема в том, что мне приходится явно выражать каждый путь к объекту, так как я не могу найти способ выразить изменение в идентификаторах объекта.

Мне бы хотелось, чтобы фильтр был более гибким, чтобы он мог отображать структуру данных из containers вплоть до любого менеджера пакетов с заголовком, который начинается с Packages, Мне нужна вся структура, а не только конечные узлы.

То, что я думаю, мне нужно, чтобы выразить мои идентификаторы объекта с чередованием или подстановочные знаки, такие как:

  • .capabilities.*.payload?
  • .capabilities.([apk, dpkg, rpm]).payload?

Я понимаю, что ни один из вышеперечисленных не является допустимым синтаксисом JQ, что является причиной для вопроса. Ниже я включил тестовый корпус с действующим JSON, а мой текущий фильтр jq указан в разделе ниже.

Минимальный файл JSON

Это мой тестовый корпус, сохраненный как minimal.json в текущем каталоге.

{
  "containers": {
    "3dc76c82e566a116e5b64bc91a0b6220c71db7052f68317ebbe90521db55bf36": {
      "container_name": "/apache-46869",
      "capabilities": {
        "apk": {
          "title": "Packages (APK)"
        },
        "dpkg": {
          "title": "Packages (DPKG)",
          "payload": {
            "apt": "1.0.9.8.4",
            "libnghttp2-14": "1.18.1-1"
          }
        },
        "rpm": {
          "title": "Packages (RPM)"
        }
      }
    },
    "474047a1fe238e39fa1917aff0c93154624bbf159d321d49d5e685302589ab51": {
      "container_name": "/nginx-alpine-46869",
      "capabilities": {
        "apk": {
          "title": "Packages (APK)",
          "payload": {
            ".nginx-rundeps": "0",
            "apk-tools": "2.6.8-r2"
          }
        },
        "dpkg": {
          "title": "Packages (DPKG)"
        },
        "rpm": {
          "title": "Packages (RPM)"
        }
      }
    },
    "d7dcd90791240d78022941cf054a6b474f5329acd79aa15b58dc342f95a8ce33": {
      "container_name": "/apache-alpine-46869",
      "capabilities": {
        "apk": {
          "title": "Packages (APK)",
          "payload": {
            ".httpd-rundeps": "0",
            "apk-tools": "2.6.8-r2",
            "apr": "1.5.2-r1",
            "apr-util": "1.5.4-r2"
          }
        },
        "dpkg": {
          "title": "Packages (DPKG)"
        },
        "rpm": {
          "title": "Packages (RPM)"
        }
      }
    }
  }
}

Явный фильтр jq

Это мой текущий фильтр, который работает, но явно определяет каждый необязательный объект indentifier-index.

jq '
    [ .containers[] | { 
        name: .container_name, package_inventory: {
            apk: .capabilities.apk.payload?,
            dpkg: .capabilities.dpkg.payload?,
            rpm: .capabilities.rpm.payload?
        }   
    }]  
' minimal.json

Ожидаемый результат

Мой текущий вывод (показан ниже) правильный. Цель не в том, чтобы исправить вывод, а в том, чтобы сделать фильтр более гибким.

[
  {
    "name": "/apache-46869",
    "package_inventory": {
      "apk": null,
      "dpkg": {
        "apt": "1.0.9.8.4",
        "libnghttp2-14": "1.18.1-1"
      },
      "rpm": null
    }
  },
  {
    "name": "/nginx-alpine-46869",
    "package_inventory": {
      "apk": {
        ".nginx-rundeps": "0",
        "apk-tools": "2.6.8-r2"
      },
      "dpkg": null,
      "rpm": null
    }
  },
  {
    "name": "/apache-alpine-46869",
    "package_inventory": {
      "apk": {
        ".httpd-rundeps": "0",
        "apk-tools": "2.6.8-r2",
        "apr": "1.5.2-r1",
        "apr-util": "1.5.4-r2"
      },
      "dpkg": null,
      "rpm": null
    }
  }
]

2 ответа

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

def payloads(keys): . as $in
  | reduce keys[] as $key ({}; .[$key] = ($in|.[$key].payload?) );

тогда ваш запрос становится:

.containers[] | { 
    name: .container_name,
    package_inventory: (.capabilities | payloads( ["apk","dpkg","rpm"] ))
}

Конечно, возможны и другие варианты. Например, вы можете определить payloads как функция arity-2, и, таким образом, переходят в "возможности".

Использование объекта JSON для указания ключей

Вот вариант payloads/1 иллюстрирующий (а) как избежать reduceи (b) как ключи могут быть указаны путем предоставления объекта JSON в качестве шаблона:

def payloads_at(object):
  . as $in
  | object as $object
  | ({}
     | [($object|keys_unsorted[]) as $key
        | .[$key] = ($in|.[$key].payload?) ])
  | add;

Это можно назвать так: payloads_at( {apk, dpkg, rpm}) или если вы хотите, чтобы ключи определялись динамически:

(.capabilities | payloads_at( . ) )

Эта вспомогательная функция, возможно, ближе к тому, что вы ищете:

def star(pre; template; post):
  pre as $object
  | ({} | [($object|template|keys_unsorted[]) as $key | .[$key] = ($object | .[$key] | post) ])
  | add;

использование

Явный список имен ключей:

star(.capabilities; {apk,dpkg,rpm}; .payload)

Ключи.capabilities:

star(.capabilities; .; .payload)

Пример:

.containers[] | { 
    name: .container_name,
    package_inventory: star(.capabilities; .; .payload)
}
Другие вопросы по тегам