Как можно отфильтровать объект JSON, чтобы выбрать только конкретные ключи / значения, используя jq? - PullRequest
1 голос
/ 04 июля 2019

Я пытаюсь проверить все версии в файле versions.json и получить в качестве вывода json только с неверными версиями.

Вот пример файла:

{
  "slamx": "16.4.0 ",
  "sdbe": null,
  "mimir": null,
  "thoth": null,
  "quasar": null,
  "connectors": {
    "s3": "16.0.17",
    "azure": "6.0.17",
    "url": "8.0.2",
    "mongo": "7.0.15"
  }
}

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

delpaths([paths(type == "string" and contains(" ") or type == "object" | not)]) 
| delpaths([paths(type == "object" and (to_entries | length == 0))])

И используйте его на оболочке так:

BAD_VERSIONS=$(jq 'delpaths([paths(type == "string" and contains(" ") or type == "object" | not)]) | delpaths([paths(type == "object" and (to_entries | length == 0))])' versions.json)

if [[ $BAD_VERSIONS != "{}" ]]; then
  echo >&2 $'Bad versions detected in versions.json:\n'"$BAD_VERSIONS"
  exit 1
fi

и получите это как вывод:

Bad versions detected in versions.json:
{
  "slamx": "16.4.0 "
}

Однако это очень запутанный способ фильтрации. Вместо того, чтобы просто пройтись по дереву путей и просто сказать «сохрани это, сохрани это», мне нужно создать список вещей, которые мне не нужны, и удалить их, дважды .

Учитывая все встроенные функции обработки пути и рекурсивную обработку, я не могу не чувствовать, что должен быть лучший способ сделать это, что-то похожее на select, но рекурсивно работать по объекту, но лучше Я мог бы сделать это:

. as $input | 
[path(recurse(.[]?)|select(strings|contains("16")))] as $paths | 
reduce $paths[] as $x ({}; . | setpath($x; ($input | getpath($x))))

Мне это не нравится по двум причинам. Во-первых, я создаю новый объект вместо того, чтобы «редактировать» старый. Во-вторых, он полон переменных, что указывает на серьезную проблему инверсии потока и добавляет сложности.

Есть идеи?

Ответы [ 2 ]

0 голосов
/ 08 июля 2019

Благодаря комментарию @ jhnc я нашел решение. Уловка заключалась в использовании потоков, что делает вложение неактуальным - я могу применять фильтры, основанные исключительно на значении, и объекты будут перекомпонованы с учетом путей к ключам.

Однако первое, что я попробовал, не сработало. Это:

jq -c 'tostream|select(.[-1] | type=="string" and contains(" "))' versions.json

возвращает [["slamx"],"16.4.0 "], что я и ищу. Тем не менее, я не мог сложить его обратно в объект. Чтобы это произошло, поток должен иметь маркеры «закрыть объект» - массивы только с одним элементом, соответствующим последнему ключу закрываемого объекта. Поэтому я изменил это на следующее:

jq -c  'tostream|select((.[-1] | type=="string" and contains(" ")) or length==1)' versions.json

Разбивая его, .[-1] выбирает последний элемент массива, который будет значением. Затем type=="string" and contains(" ") выберет все значения, которые являются строками и содержат пробелы. Последняя часть выбора, length==1, сохраняет все маркеры «конца». Интересно, что он работает, даже если маркер конца не соответствует последнему ключу, так что это может быть хрупким.

После этого я могу снять его с потока:

jq -c  'fromstream(tostream|select((.[-1] | type=="string" and contains(" ")) or length==1))' versions.json

Выражение jq выглядит следующим образом:

fromstream(
    tostream |
    select(
        (
            .[-1] | 
            type=="string" and contains(" ")
        ) or 
        length==1
    )
)
0 голосов
/ 04 июля 2019
  1. Для объектов тест to_entries|length == 0 может быть сокращен до length==0.

  2. Если я правильно понимаю цель, вы можете просто использовать .., возможно, по следующим строкам:

..
| objects
| with_entries(
    select(( .value|type == "string" and contains(" ")) or (.value|type == "object" and length==0)) )
| select(length>0)

пути

Если вы хотите пути, то подумайте:

([], paths) as $p
| getpath($p)
| objects
| with_entries(
        select(( .value|type == "string" and contains(" ")) or (.value|type == "object" and length==0)) )
| select(length>0) as $x
| {} | setpath($p; $x)

Если ваш входной сигнал изменен таким образом, чтобы у s3 был завершающий пробел, вышеприведенное выдает:

{"slamx":"16.4.0 "}
{"connectors":{"s3":"16.0.17 "}}
...