Я не понимаю, почему при фильтрации всех элементов, которые являются объектами и массивами, затрагиваются также нулевые значения.
И я пока не нашел обходного пути.
Вход:
$ cat test.json
{
"object": {
"key1": 1,
"key2": null,
"key3": 3
},
"array": [ "a", null, "c" ]
}
Фактическое поведение, неожиданно отсутствующий вывод для нулевых значений:
$ jq '[ paths(select(type | IN("object", "array") | not)) as $path | { "\($path)": getpath($path) } ] | add' test.json
{
"[\"object\",\"key1\"]": 1,
"[\"object\",\"key3\"]": 3,
"[\"array\",0]": "a",
"[\"array\",2]": "c"
}
Желаемый (и ожидаемый) результат:
{
"[\"object\",\"key1\"]": 1,
"[\"object\",\"key2\"]": null,
"[\"object\",\"key3\"]": 3,
"[\"array\",0]": "a",
"[\"array\",1]": null,
"[\"array\",2]": "c"
}
Обновлено:
Как мы видим здесь, пути с нулевыми значениями выводятся, если фильтр не применяется:
$ jq '[ paths as $path | { "\($path)": getpath($path) } ] | add' test.json
{
"[\"object\"]": {
"key1": 1,
"key2": null,
"key3": 3
},
"[\"object\",\"key1\"]": 1,
"[\"object\",\"key2\"]": null,
"[\"object\",\"key3\"]": 3,
"[\"array\"]": [
"a",
null,
"c"
],
"[\"array\",0]": "a",
"[\"array\",1]": null,
"[\"array\",2]": "c"
}

paths/1 (см. его определение) уже применяется select/1 с использованием предоставленного вами фильтра узлов. Ваш подход не сработал, потому что ваш фильтр узлов также применяет еще один select/1 к результату, вызывая фильтрацию ложных значений (null и false). Удалите select/1 из фильтра узлов, и все заработает:
[ paths(type | IN("object", "array") | not) as $path
| { "\($path)": getpath($path) }
] | add
{
"[\"object\",\"key1\"]": 1,
"[\"object\",\"key2\"]": null,
"[\"object\",\"key3\"]": 3,
"[\"array\",0]": "a",
"[\"array\",1]": null,
"[\"array\",2]": "c"
}
В качестве альтернативы, особенно если вы включили select/1 для имитации применения ярлыков , таких как iterables/0 (выбор только массивов и объектов) или scalars/0 (наоборот), которые сами по себе также все реализованы на основе select/1 (таким образом, непригодны для использования здесь с paths/1), вы можете переключиться на нерекурсивный path/1 фильтр (без «s») и вручную включить рекурсию, например используя рекурсивный спуск ..:
[ path(.. | select(type | IN("object", "array") | not)) as $path
| { "\($path)": getpath($path) }
] | add
# or
[ path(.. | scalars) as $path
| { "\($path)": getpath($path) }
] | add
В зависимости от вашего конкретного варианта использования вас также может заинтересовать функция jq по преобразованию входных данных в «потоковое представление», которое уже предоставляет вам структуру [path, value] с использованием фильтра tostream/0 или опции --stream. Просто отфильтруйте элементы возврата (без значения), выбрав их по индексу 1.
jq '[tostream | select(has(1)) | {("\(first)"): last}] | add'
# or
jq --stream -s 'map(select(has(1)) | {("\(first)"): last}) | add'
# or
jq --stream -n 'reduce (inputs | select(has(1))) as [$p,$v] ({}; .["\($p)"] = $v)'
Чтобы прояснить то, чего я не понимаю: почему отрицание type | in ("object", "array") не верно для типа «null»?
@yaccob paths/1определен уже с использованием select с вашим пользовательским фильтром. Ваш подход не сработал, потому что вы обернули вокруг него еще один select, в результате чего были отфильтрованы ложные значения (null и false). Уберите select, и всё заработает: [ paths(type | IN("object", "array") | not) as $path | …
@yaccob Сам запутался: использование scalars (определено как select(type|. != "array" and . != "object")), конечно, имеет ту же проблему (фильтрация значений false), поэтому я заменил всю эту часть ответа содержимым последнего комментария.
теперь я понял проблему, многому научился из ваших четких ответов, большое спасибо
Хотя я до сих пор не до конца понимаю, почему мой подход не работает, ваше решение кажется мне намного лучше, проще и понятнее! Также существуют решения, использующие потоковые представления. Большое спасибо!