Перейти к содержимому
Поиск
Разработка

jq: JSON в консоли — мастерская и 10 практических задач

Обрабатываем JSON с jq: десять реальных задач, редактируемые фильтры, NDJSON-журналы, CSV, параметры и скачивание результата. Исходники для Windows и Linux.

Александр Некрасов
jq — фильтры JSON, группировка и преобразование данных

jq на практике: JSON, API, NDJSON-журналы, отчёты и CSV. Десять настоящих задач в браузере, готовые фильтры и исходники для Windows, Linux и VPS.

Что такое jq и какой результат он даёт

Вы получили большой JSON от API: сотни заявок, вложенные клиенты, служебные поля и параметры доставки. Для сегодняшней задачи нужны только оплаченные работы, сгруппированные по городу. Можно написать отдельный скрипт, открыть документ в редакторе или вручную копировать строки. jq позволяет выразить преобразование короткой программой, сохранить её в файл и повторять на следующих выгрузках.

jq — консольный обработчик JSON. Он читает данные, применяет фильтр и выводит результат: другой JSON, поток значений или текст. Фильтр умеет выбирать поля, отбирать элементы, строить объекты, сортировать, группировать и соединять данные. Это полезный инструмент разработчика, администратора сервера и автора интеграций, когда нужен прозрачный, воспроизводимый шаг обработки.

В статье десять законченных задач, настоящая мастерская на jq 1.8.2 и исходники. Вы сможете изменить JSON прямо на странице, проверить фильтр, скачать входные данные и результат, затем выполнить ту же задачу на Windows или Linux. Мы разберём не только синтаксис: отдельно покажем параметры, ошибки, кодировку, большие файлы и границы точности чисел.

Где применять: API, журналы, отчёты и автоматизация

При диагностике API jq помогает быстро выделить нужные идентификаторы и статусы из сохранённого ответа. При сопровождении сайта — собрать статистику ошибок из JSON-журнала. В интеграции — привести выгрузку поставщика к ожидаемому набору полей. В процессе сборки — прочитать версию из JSON-манифеста или проверить обязательное значение перед следующим шагом.

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

Для сложной бизнес-логики, долговременного хранения и конкурентного изменения данных обычно нужен полноценный сервис или база. jq удобно использовать на границе: подготовить, проверить, преобразовать и передать дальше. Если преобразование должно жить внутри приложения, сравните этот подход с JSONata; если нужны связи, индексы и транзакции — с SQLite.

Как работает фильтр: значение, поток и конвейер

В jq точка обозначает текущий вход. Выражение .orders получает поле orders, .orders[] выдаёт элементы массива по одному, а map(…) собирает преобразованные элементы в новый массив. Вертикальная черта передаёт результат следующему фильтру. Это конвейер внутри jq; он отличается от конвейера оболочки, который соединяет отдельные процессы.

.orders | map(select(.status == "paid")) | map({id, amountMinor})

Важно различать один JSON-массив и несколько самостоятельных JSON-документов. Первый удобно читать приложению одним JSON.parse. Второй удобен для построчной обработки, особенно с компактным выводом -c. Если после .[] вы видите несколько объектов подряд, это не потерянные квадратные скобки: фильтр намеренно создал поток результатов.

В мастерской выход сохраняется исходным текстом jq. Интерфейс не разбирает его ради красивого повторного JSON.stringify: иначе большие числовые литералы могли бы незаметно округлиться уже в JavaScript. При дальнейшем использовании файла определите контракт: один документ, NDJSON либо обычный текст. От этого зависит правильный парсер на следующем шаге.

Версии: официальный jq и браузерная сборка

Примеры закреплены на jq 1.8.2. Для обычной консоли используется официальный исполняемый файл проекта jqlang; проверенный загрузчик из архива получает его с GitHub Releases и сверяет SHA-256 до сохранения. Версию можно проверить командой jq –version. Системный пакет дистрибутива может иметь другой номер: это нужно учитывать при воспроизведении.

В браузере работает то же ядро jq 1.8.2, собранное в WebAssembly сторонним проектом WASM Zoo. В package-lock закреплён @wasm-zoo/jq 0.9.1. Это не официальный браузерный выпуск jqlang; название и происхождение указаны в интерфейсе и README. К WASM приложены лицензии, информация о сборке и происхождении компонентов.

Наш адаптер создаёт виртуальные файлы и запускает jq в отдельном Worker. Пользовательский фильтр не превращается в JavaScript через eval и не становится shell-командой. Все библиотеки загружаются с того же сайта. Подробнее об установке и изменениях: официальные загрузки, релиз 1.8.2, WASM Zoo.

Мини-мастерская: попробуйте десять реальных задач

Выберите задачу, прочитайте, где она пригодится, и нажмите «Выполнить jq». Затем измените один параметр и сравните результат. Для фильтра по сумме раскройте параметры: значения лежат в отдельном JSON-документе. Для NDJSON и CSV уже включены нужные флаги, поэтому можно сразу наблюдать соответствующий формат.

На странице есть отдельные скачивания input.json, filter.jq, params.json, результата и инструкции запуска. После изменения любого входа старый результат становится неактуальным и его скачивание отключается до нового успешного прогона. Ошибочный JSON или синтаксис фильтра отображается отдельно, вместе с кодом завершения; следующий исправленный запуск не наследует состояние предыдущего.

Это учебная мастерская для небольших документов: ввод до 128 КиБ, фильтр до 8 КиБ, параметры до 16 КиБ, вывод до 256 КиБ. Запуск ограничен четырьмя секундами, включая загрузку WASM; кнопка остановки завершает Worker. Для длинных запросов и больших файлов переносите проверенный фильтр в локальную консоль.

Установка и первый запуск в Windows

Для системной установки официальный сайт предлагает winget. После установки откройте новый терминал: уже работающий процесс может ещё не видеть обновлённый PATH. Проверка версии важнее сообщения установщика, потому что на компьютере иногда находится несколько копий jq. В PowerShell можно дополнительно выполнить Get-Command jq и посмотреть фактический путь.

winget install jqlang.jq
jq --version
Get-Command jq
jq -M -f filter.jq input.json

Для воспроизведения всего проекта распакуйте архив и используйте Node.js 22 или новее. Он нужен для сборки мастерской, тестов и удобного загрузчика; сама нативная утилита jq не зависит от Node.js. Команда setup:jq сохраняет проверенный binary только в tools/bin текущего проекта и не меняет настройки системы.

npm.cmd ci --ignore-scripts
npm.cmd run build
npm.cmd test
npm.cmd run setup:jq
npm.cmd run test:native
node tools/run-example.mjs select
npm.cmd run serve

Локальная мастерская откроется по адресу http://127.0.0.1:8183/. Остановить сервер можно Ctrl+C. Не открывайте index.html двойным щелчком через file://: для Worker и WebAssembly браузеру нужен нормальный HTTP-контекст. Для запуска отдельного CLI-примера веб-сервер вообще не требуется.

Linux: локальная работа и выделенный сервер

На Ubuntu и Debian jq доступен в штатном репозитории. Это удобный вариант для ежедневной работы по SSH; пакет обновляется обычным способом обслуживания ОС. Номер версии зависит от выпуска дистрибутива. Если необходима точная версия наших примеров, используйте загрузчик из исходников: он выбирает Linux x64 или arm64 и проверяет официальный SHA-256.

sudo apt update
sudo apt install jq
jq --version
# Reproducible project runtime:
npm ci --ignore-scripts
npm run build
npm run setup:jq
npm run test:native
./tools/bin/jq -M -f fixtures/fields/filter.jq fixtures/fields/input.json

На сервере фильтры можно хранить рядом с кодом задания, входные выгрузки — в каталоге с ограниченными правами, а результаты — отдельно от оригиналов. jq не требует полноценного веб-приложения: его удобно запускать из bash, systemd job или существующего процесса доставки данных. Права определяются учётной записью, от которой запущена команда.

Для просмотра удалённой мастерской оставьте сервер на 127.0.0.1 и создайте SSH-туннель со своего компьютера: ssh -L 8183:127.0.0.1:8183 user@your-vps. Тогда локальный браузер увидит удалённый сервис через туннель. Для постоянной публичной публикации лучше раздавать готовую папку public существующим HTTPS-сервером, а не держать учебный Node-сервер открытым наружу.

Кавычки, UTF-8 и почему фильтр лучше хранить файлом

Одна и та же короткая строка может по-разному интерпретироваться в bash, cmd.exe и разных поколениях PowerShell. Внутри фильтра встречаются кавычки, скобки и знак доллара, которые имеют значение и для оболочки. Самый удобный переносимый вариант — сохранить программу в filter.jq и передать её через -f.

jq -M --slurpfile params params.json -f filter.jq input.json
# PowerShell: inspect immediately after jq
$LASTEXITCODE
# bash: inspect immediately after jq
echo $?

Исходники проекта и скачиваемые файлы записаны в UTF-8. Для перенаправления stdout нативной программы без преобразования байтов в Windows используйте PowerShell 7.4 или новее. Старый Windows PowerShell 5.1 имеет другое поведение. Это существенно для русских строк, CSV и обмена с приложением, ожидающим определённую кодировку.

В архиве есть tools/export.mjs: он получает stdout как байты, проверяет код завершения и создаёт новый файл без перезаписи существующего. Это даёт одинаковый контролируемый путь сохранения на обеих ОС. Особенности перенаправления описаны в документации PowerShell.

Пример 1. Выбрать поля ответа API

В учебном ответе каждая заявка содержит клиента, город, сумму, название и статус. Для небольшого списка кабинета оставим три понятных поля. Это делает контракт результата очевидным: следующий потребитель получает только то, что ему действительно нужно. Новое поле исходного API не попадёт в результат само по себе.

.orders | map({id, name, status})

Попробуйте добавить amountMinor в объект справа. Затем измените источник: добавьте телефон, комментарий или служебный флаг. Эти сведения останутся за пределами результата, пока фильтр не начнёт выбирать их явно. Такой allowlist обычно проще проверять, чем длинный список удаляемых полей неизвестного документа.

Если нужен один элемент на строку для следующего процесса, используйте .orders[] | {id, name, status} и флаг -c. Если HTTP-клиент ожидает массив, оставьте map. jq помогает преобразовать уже полученный ответ, но не управляет пагинацией исходного API: прежде чем считать полный отчёт, убедитесь, что выгружены все необходимые страницы.

Пример 2. Фильтрация с параметрами из файла

Порог суммы и нужный статус часто меняются, а программа преобразования остаётся одинаковой. В мастерской они вынесены в params.json. Флаг –slurpfile params читает документы файла в массив $params; в наших примерах там ровно один объект, поэтому используется $params[0]. Сам фильтр не приходится переписывать под каждый запуск.

.orders
| map(select(.status == $params[0].status
             and .amountMinor >= $params[0].minMinor))
| map({id, amountMinor})

При paid и пороге 10000 останутся A-101, A-105 и A-106. Поднимите значение до 13000: останется только A-105. Это маленькая проверка бизнес-смысла выборки. Порог указан в копейках, а не в рублях; название поля подчёркивает единицу и помогает избежать ошибки в сто раз.

Параметры остаются данными. Не склеивайте их со строкой программы через конкатенацию shell-команды. Для одиночных значений jq также предлагает –arg и –argjson, но файл удобнее для набора настроек и переносимости. На сервере до запуска задайте допустимые диапазоны и проверьте структуру параметров: пустой либо неверно сформированный документ должен давать понятную ошибку.

Пример 3. Отсутствующее значение и явное false

Необязательные поля кажутся простыми, пока рядом не оказываются null, пустая строка, отсутствие ключа и логическое false. Они могут означать разные решения пользователя. В третьей задаче имя null заменяется на подпись «Без имени», а пустая строка сохраняется. Настройка уведомлений false тоже должна остаться false.

map({id,
  name: (.name // "Без имени"),
  notify: (if has("notify") then .notify else true end)
})

Замените выражение для notify на .notify // true и посмотрите на первую анкету. Результат изменится: // подставляет правую часть также для false. Поэтому этот оператор подходит не для каждого значения по умолчанию. Проверка has отвечает на более узкий вопрос — присутствует ли ключ.

В интеграциях договоритесь, что означает каждый вариант. Пустая строка может быть ошибкой формы, а null — командой очистить ранее заполненное значение. Если механически превратить всё в один текст, следующая система уже не сможет восстановить намерение. В тестовом наборе полезно держать все четыре случая, даже когда обычные записи выглядят одинаково.

Пример 4. Группировка и сводные показатели

Для отчёта по городам сначала оставим оплаченные заявки, затем сгруппируем их и сформируем по одному объекту на город. В каждой группе считаем число строк и сумму amountMinor. В исходном наборе получаются Казань — 17000, Москва — 21400, Тула — 12500 копеек. Эти небольшие целые числа удобны для проверки вручную.

.orders | map(select(.status == "paid"))
| group_by(.city)
| map({city: .[0].city, orders: length,
       totalMinor: (map(.amountMinor) | add)})
| sort_by(.city)

Измените город A-106 и выполните фильтр повторно. Запись переместится между группами, изменив обе сводки. Затем поменяйте статус на cancelled: она исчезнет из расчёта. Так видно, что KPI — результат конкретного правила отбора, а не просто сумма всех чисел из документа.

Перед использованием в реальном отчёте уточните состав выгрузки, валюту, статусы возвратов, дубликаты и период. Складывать рубли и другую валюту в одном поле нельзя. Учебный пример показывает преобразование структуры; правила выручки или бухгалтерского учёта должны быть определены отдельно. Для точной денежной арифметики используйте подходящую decimal-библиотеку, а не полагайтесь на числовой тип jq.

Пример 5. Сортировка и постраничный результат

Пятая задача показывает три самых крупных заявки. Мы сортируем по отрицательной сумме, чтобы получить убывание, а при равенстве — по идентификатору. Дополнительное поле делает порядок воспроизводимым. Без согласованного правила равные значения могут визуально менять положение между разными этапами обработки.

.orders | sort_by([-.amountMinor, .id])
| .[$params[0].offset:($params[0].offset + $params[0].limit)]
| map({id, amountMinor})

Сдвиг offset и размер limit находятся в параметрах. Поставьте offset 1: первая позиция исчезнет, а следующая страница начнётся со второго элемента отсортированного массива. Порядок операций здесь важен: сначала сортировка всего набора, затем срез. Сортировка отдельно каждой уже полученной страницы не создаёт глобальный рейтинг.

Такой подход подходит для небольшого локального отчёта. Он не заменяет серверную пагинацию базы с большим числом строк: jq сначала должен получить и отсортировать все данные. Если источник уже умеет фильтр, ORDER BY и LIMIT, часто выгоднее выполнить основную работу там, а jq оставить подготовку окончательного JSON.

Пример 6. Из массива заявок — объект по клиентам

При обмене между сервисами форма данных бывает важнее отдельных значений. Один сервис отдаёт плоский массив заявок, другой ожидает объект, где ключ — идентификатор клиента. Сгруппируем записи по customerId, построим пары key/value и превратим их в объект. У каждого клиента сохранится массив его заявок.

.orders | group_by(.customerId)
| map({key: .[0].customerId, value: map({id, status})})
| from_entries

Полученная структура удобна для адресного обращения по клиенту и формирования отдельных сообщений. Но ключи объекта уникальны: если строить from_entries прямо из заявок, повторяющийся customerId мог бы привести к замене значения. Предварительная группировка делает требуемое отношение «один клиент — несколько заявок» явным.

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

Пример 7. NDJSON-журнал и статистика ошибок

Многие приложения пишут журнал как NDJSON: одна завершённая строка содержит один JSON-объект. Между строками нет запятых и общей пары квадратных скобок. jq понимает поток документов, а флаг -s собирает его в массив. В мастерской этот флаг уже включён для сценария журнала.

map(select(.status >= 500)) | group_by(.route)
| map({route: .[0].route, errors: length,
       maxMs: (map(.ms) | max)})

Для /api/orders получится две ошибки и максимальная задержка 110 мс, для /api/search — одна ошибка и 140 мс. Добавьте новую строку с 503: изменится соответствующая группа. Такой быстрый отчёт полезен при первом разборе инцидента, когда нужно понять, какой маршрут чаще сообщает о сбое.

Не собирайте многогигабайтный журнал через -s без оценки памяти. Для построчного отбора можно использовать select(.status >= 500) с -c, передавая дальше только подходящие строки. Неполная последняя запись, повреждённый JSON и несовпадающая схема требуют явной обработки. Обычный текстовый журнал нельзя считать NDJSON только потому, что несколько строк похожи на объекты.

Пример 8. Правильный CSV и отдельная проблема формул

CSV нельзя надёжно собирать простым соединением значений через запятую. Название организации может содержать запятую, кавычки или перевод строки. В восьмой задаче такие данные уже есть: компания «Север», филиал и заметка в две строки. Фильтр @csv создаёт корректное структурное экранирование для каждого массива значений.

(["id", "company", "note"] | @csv),
(.[] | [.id, .company, .note] | @csv)

Запускайте этот фильтр с -r, чтобы jq вывел строки CSV без дополнительных JSON-кавычек. Один и тот же экспорт можно сохранить tools/export.mjs: node tools/export.mjs csv result.csv. Файл создаётся только при успешном завершении jq; существующий result.csv не будет перезаписан этой командой.

CSV-экранирование решает структуру файла, но не интерпретацию формул в Excel или другом табличном редакторе. Значения с =, +, – или @ могут требовать специальной политики для конкретного получателя. Не добавляйте апострофы бездумно в машинный обмен: это меняет данные. Для PHP-сценариев импорта и экспорта есть отдельная статья о League CSV.

Пример 9. Удаление известных полей из учебного документа

Перед обсуждением ошибки часто хочется убрать из JSON несколько служебных полей. В девятом сценарии walk обходит вложенные значения, а для объектов удаляются password, token и authorization. Все строки в исходнике демонстрационные. Попробуйте добавить token глубже: он тоже исчезнет после выполнения.

walk(if type == "object" then del(.password, .token, .authorization) else . end)

Этот приём полезен для известной схемы, но не является универсальным обезличивателем. Он не распознает секрет, записанный в message, другом регистре имени или неизвестном поле. Также он не понимает, какие сведения в конкретной организации считаются внутренними. Перед передачей реального документа его нужно проверять по содержанию и назначению.

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

Пример 10. Соединение заявок со справочником

API часто отдаёт customerId в заявке и отдельный справочник клиентов. Для читаемого отчёта нужно подставить название. В десятой задаче INDEX(.id) строит объект для поиска клиента, после чего каждая заявка получает соответствующее имя. Исходный корневой объект сохранён в переменной $root, чтобы обе коллекции оставались доступны.

. as $root
| ($root.customers | INDEX(.id)) as $clients
| $root.orders | map({id, customerId,
    customer: ($clients[.customerId].name // "Неизвестный клиент"),
    amountMinor})

В наборе специально нет клиента C-4: у A-106 появится явная подпись «Неизвестный клиент». Это удобнее для оператора, чем незаметная потеря заявки. В другом процессе отсутствие справочника может быть критической ошибкой — тогда вместо подписи следует остановить подготовку и перечислить проблемные идентификаторы.

INDEX предполагает понятное поведение уникальных ключей. Если справочник содержит дубликаты id, сначала обнаружьте и разберите их: выбор одной записи может скрыть конфликт. Для больших связанных наборов и повторяющихся запросов лучше база данных с ограничением уникальности и JOIN. jq особенно удобен для разовой подготовки небольшого обменного файла.

Точность чисел: длинный ID не должен стать другим

JSON не диктует каждой реализации бесконечную точность. Современная сборка jq с decnum способна сохранять исходное представление многих числовых литералов, пока не начались арифметические операции. Это полезное свойство, но не обещание точного финансового расчёта: вычисления могут переходить к IEEE754, а конфигурация сборки тоже влияет на поведение.

{id: "9007199254740993", amount: "123456789.0123456789"}
# Keep identifiers and exact decimal amounts as strings.
# Arithmetic demonstration:
0.1 + 0.2

В тестах проекта длинный идентификатор и десятичная строка сохраняются буквально, а 0.1 + 0.2 даёт 0.30000000000000004. Результат ожидаемый и специально проверяется, чтобы читатель не спутал удобное преобразование JSON с библиотекой денежных вычислений. Даже целые minor units перестают быть универсальным решением при выходе за допустимый диапазон точности.

Выбирайте представление на уровне контракта: идентификатор обычно строка, точная десятичная сумма — строка с согласованной валютой и масштабом, обычные счётчики — числа в известном диапазоне. Не преобразуйте строку в число только ради сортировки, если значение может быть очень длинным. После jq проверьте, что следующая библиотека не округляет данные при чтении.

Ошибки, stderr и коды завершения

У команды есть минимум три результата: stdout, stderr и код завершения. Пустой stdout может быть нормальным итогом фильтра, который не нашёл подходящих элементов. Текст ошибки нельзя смешивать с JSON-ответом: иначе следующий парсер увидит посторонние строки. В мастерской эти потоки отображаются отдельно.

jq -e -f check.jq input.json
# -e: last result false/null -> exit 1; no result -> exit 4.
# Example check.jq:
type == "array" and length > 0

Флаг -e полезен для проверки условия в автоматизации, но меняет смысл успешного выполнения. Фильтр может корректно вычислить false, и процесс завершится с кодом 1 именно из-за проверки. Без этого флага само значение false не означает сбой программы. Поэтому обрабатывайте статусы в соответствии с назначением конкретного шага.

Ошибка синтаксиса фильтра, повреждённый вход и нарушение ожиданий схемы — разные причины. Сначала воспроизведите проблему на небольшом файле, затем добавьте этот случай в тесты. Не прячьте все ошибки через try … catch null: так временная проблема превращается в правдоподобное отсутствие данных. Для необязательного поля null допустим, для неуспешной выгрузки он обычно недостаточен.

Запуск jq из Node.js и PHP

Если преобразование становится частью сервиса, запускайте jq с массивом аргументов и без shell. Тогда путь к входному файлу и текст параметра не превращаются в фрагменты командной строки оболочки. В tools/run-example.mjs и tools/export.mjs есть рабочие примеры; библиотека jq при этом остаётся отдельным процессом.

const result = spawnSync(jqPath, [
  '-M', '--slurpfile', 'params', 'params.json',
  '-f', 'filter.jq', 'input.json'
], { shell: false, timeout: 5000, maxBuffer: 262144 });
if (result.error || result.status !== 0) {
  throw new Error('jq transformation failed');
}
writeFileSync('result.json', result.stdout, { flag: 'wx' });

Для HTTP-приложения также потребуются ограничение числа одновременных процессов и управление временными файлами. Фильтр должен быть вашим проверенным кодом либо выполняться в специально ограниченной среде. Не создавайте публичный endpoint, который принимает любую команду или путь к серверному файлу ради демонстрации jq.

В PHP аналогичный подход удобно реализовать через Symfony Process: argv-массив, явный timeout, проверка завершения и отдельное чтение вывода. Для простого изменения уже разобранного массива иногда проще остаться в PHP. Внешний процесс оправдан, когда вы переиспользуете проверенный фильтр или строите единый инструмент для разных окружений.

Получение данных из API: jq отвечает за преобразование

jq не является HTTP-клиентом. Обычно перед ним стоит curl, приложение или другой загрузчик. Сначала убедитесь, что запрос завершился успешно, получен ожидаемый тип ответа и размер допустим. HTML-страница ошибки, сохранённая под именем response.json, останется HTML независимо от расширения файла.

# Linux bash; URL is a placeholder for your own endpoint.
set -euo pipefail
curl --fail --silent --show-error --max-time 15   'https://api.example.org/orders' -o response.json
jq -M -f filter.jq response.json > result.json

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

Если нужна пагинация, повтор запроса или ограничение скорости, это обязанности загрузчика. Фильтр jq видит только полученный набор и не узнает, что API обрезал выдачу. Поэтому в отчёт полезно добавлять метаданные о времени выгрузки, количестве страниц и исходной выборке. Красивый JSON не доказывает полноту данных.

Большие файлы и граница памяти

Вызов select над отдельными документами потока может обрабатывать записи последовательно. Однако -s, сортировка и группировка требуют удерживать набор в памяти. Если файл стал большим, недостаточно убрать ограничение мастерской и надеяться на тот же сценарий: нужно пересмотреть алгоритм и место обработки.

# Post-by-post filtering of NDJSON, without -s:
jq -c 'select(.status >= 500)' app.ndjson > errors.ndjson
# Count resulting documents after filtering, for moderate input:
jq -s 'length' errors.ndjson

Для действительно больших JSON-документов jq предлагает потоковый режим –stream, но там меняется форма входа фильтра: вместо привычного объекта поступают пути и значения. Это отдельная модель обработки. Часто проще изменить экспорт источника на NDJSON либо выполнять агрегацию в базе, чем переписывать сложный алгоритм вокруг огромного вложенного объекта.

Ограничение stdout в нашей мастерской предотвращает накопление длинного потока строк, а timeout завершает Worker. Оно не является строгим лимитом всей памяти: гигантская строка может выделиться внутри jq до печати. Поэтому ввод ограничен, выполнение изолировано от интерфейса, а произвольные тяжёлые эксперименты лучше проводить локально с ресурсными ограничениями ОС.

Практический процесс на VPS: вход, проверка, отдельный результат

Для ежедневного отчёта разделите этапы: получить выгрузку, убедиться в её корректности, выполнить сохранённый фильтр, проверить результат и только затем передать потребителю. Важный принцип — исходный файл не должен становиться целью перенаправления. Конструкция jq … input.json > input.json может обнулить вход ещё до того, как jq начнёт его читать.

node tools/export.mjs group report-new.json
# Existing report-new.json is not overwritten.
# Review/validate the new file before handing it to the next stage.
./tools/bin/jq -e 'type == "array" and length > 0' report-new.json

На Linux используйте отдельного пользователя задания и каталоги с понятными владельцами. Если выполнение запускает systemd или cron, задайте абсолютные пути: интерактивный терминал и планировщик могут иметь разный PATH и рабочий каталог. В журнал записывайте код завершения, число обработанных записей и имя задания, а не полный JSON с чувствительными полями.

Первое применение не требует большого оркестратора. Достаточно одного контролируемого задания и файла с ожидаемым результатом. По мере роста добавляйте блокировку от параллельных запусков, временный файл, атомарную замену результата в одной файловой системе и ротацию. Конкретная процедура зависит от потребителя: отчёт на странице и платёжная интеграция требуют разных гарантий.

Как разместить мастерскую на своём сайте

После npm run build папка public содержит готовые HTML, CSS, JavaScript, WASM и уведомления о лицензиях. Её можно раздавать как обычную статику. Для серверной части мастерской PHP и Node не нужны: обработка происходит в браузере. Исходный проект при этом содержит CLI-примеры, которыми можно пользоваться отдельно.

Проверьте MIME-типы: JavaScript должен приходить как JavaScript, а WASM — как application/wasm. Не ограничивайтесь тем, что index.html открывается: выполните настоящий фильтр на опубликованном хосте. В проекте есть CSP, разрешающая локальные Worker и WebAssembly без сторонних CDN. Публичная страница не предоставляет выполнение shell-команд.

В README приведены примеры размещения через Caddy и Nginx. Добавляйте location или отдельный сайт к существующей конфигурации осознанно: пример с доменом example.org не должен заменять настройки работающего приложения. Сначала проверьте конфигурацию, затем применяйте reload.

Что входит в исходники и как проверять свои изменения

В архиве есть десять папок fixtures с входом, фильтром, параметрами, флагами и ожидаемым результатом. Та же коллекция используется интерфейсом и тестами, поэтому текст на странице не живёт отдельно от выполняемого примера. После правки сценария пересоберите public и проверьте нативный jq вместе с WASM.

npm run build
npm test
npm run setup:jq
npm run test:native
node tools/run-example.mjs join
node tools/export.mjs csv result.csv

Проверки WASM выполняют десять сценариев, ошибки JSON и фильтра, точность строк, ограничения ввода, сохранение нулевого байта и UTF-8 в raw-выводе, избыточный вывод, реальную отмену Worker и бесконечное вычисление с timeout. Нативные проверки выполняют те же fixtures официальным binary и отдельно проверяют коды завершения. Эти уровни не заменяют проверку интерфейса на телефоне или работу опубликованного сервера.

Добавляйте тест не на факт отсутствия исключения, а на полезный контракт. Например, сумма по городам не должна включать cancelled, false не должен превращаться в true, а неизвестный клиент не должен исчезать из списка. Такие ожидания помогают менять фильтр уверенно и объясняют его смысл следующему разработчику.

Частые вопросы: пустой результат, кавычки и новая версия

Если результат пустой, сначала проверьте, ожидает ли фильтр массив, объект или поток, и совпадает ли путь .orders с реальным документом. Затем временно упростите программу до точки и добавляйте этапы по одному. Так легче найти место, где select отбрасывает все записи или неверное поле превращается в null.

Если вместо CSV видны обратные слеши и кавычки вокруг каждой строки, проверьте -r. Если вы получили несколько красивых объектов, а приложение ждёт один массив, используйте map или соберите поток в квадратные скобки внутри фильтра. Если русские буквы повреждаются только при сохранении, сравните версию оболочки и способ перенаправления.

При обновлении jq или WASM-пакета изменяйте версию и lock намеренно, повторяйте сборку, fixtures и негативные случаи. Не заменяйте один бинарный файл без проверки checksum и требований окружения. Установка новой версии — хороший повод снова проверить точность чисел, коды завершения и поведение импорта, от которых зависит ваш конкретный процесс.

Исходники и материалы для продолжения

Начните с одной своей небольшой выгрузки, удалив из учебной копии лишние сведения. Опишите ожидаемый результат человеческим языком, возьмите похожий сценарий из мастерской и измените фильтр. Когда результат совпадёт с ожиданием, сохраните вход, параметры и программу рядом. Из такого набора получается воспроизводимый инструмент, который можно передать коллеге.

Официальный учебник jq помогает последовательно освоить конвейеры, а справочник пригодится для конкретных операторов. Контрольные суммы 1.8.2 позволяют сверить загруженный binary. Собственные исходники этой мастерской доступны под MIT; сторонние компоненты сохраняют свои лицензии.