Конвертація JSON у YAML у командному рядку: чотири пастки за однією командою yq
Головне: Найчастіше копійована в підручниках команда yq -P input.json > output.yaml насправді все ще видає JSON. У статті yq v4.44.6, PyYAML 6.0.3 і js-yaml прогнані на зразку 2.39 MB, пояснюється, чому формат виводу визначає розширення вхідного файла, і даються правильні рецепти для пакетної конвертації, лапок, відступів і точності великих цілих чисел.

Висновок: yq -P input.json > output.yaml — команда, яку найчастіше копіюють з підручників, — на yq v4 усе ще продукує JSON. Щоб отримати YAML, треба явно передати -o=yaml (або -oy). -P — це скорочення від --prettyPrint: воно керує відступами й блоковим стилем, а не форматом виводу.
Конвертація JSON у YAML ніколи не зводилася до «змінити розширення файла». Справжня ціна ховається в чотирьох місцях: формат виводу визначає розширення вхідного файла, правила лапок непомітно переписують ваші рядки, відступи послідовностей впливають на downstream-інструменти, а велике ціле число, пройшовши через yq, назавжди втрачає точність.
1. Спершу запустіть найпопулярнішу команду
На чистій машині зі встановленим yq v4.44.6 створіть мінімальний вхід:
printf '{"a":1,"b":{"c":2}}' > s.json
yq -P '.' s.json
Повертається не YAML, а JSON з відступами:
{
"a": 1,
"b": {
"c": 2
}
}
Якщо ви, як і в підручнику, перенаправите це в output.yaml, отримаєте файл із розширенням .yaml і вмістом JSON. Будь-який парсер YAML 1.2 читає його без проблем (у YAML 1.2 JSON є підмножиною — повний вивід цього відношення включення лежить у межах конвертації JSON ↔ YAML та XML), тож одразу нічого не ламається. Воно ламається в той день, коли хтось додасть коментар або якір, або коли yamllint у CI почне сипати сотнями truthy value should be one of.
Варіант, який справді видає YAML, явно називає формат виводу:
yq -o=yaml '.' s.json
# a: 1
# b:
# c: 2
2. Чому так: розширення визначає формат виводу
yq v4 працює за правилом «вхідний формат визначає вихідний». Формат виводиться з розширення вхідного файла, і без -o цей висновок використовується повторно: вхід .json → вивід JSON; вхід .yaml → вивід YAML; читання з stdin без розширення → типово YAML.
Один і той самий вхід, чотири варіанти виклику, виміряні результати:
| Команда | Джерело входу | Фактичний формат виводу | Вердикт |
|---|---|---|---|
yq -P '.' s.json |
файл s.json |
JSON (з відступами) | пастка, якщо лити в .yaml |
yq -o=yaml '.' s.json |
файл s.json |
YAML (блоковий стиль) | правильно |
yq -P -o=yaml '.' s.json |
файл s.json |
YAML (блоковий стиль) | правильно, тут -P зайвий |
cat s.json | yq -P '.' |
stdin, без розширення | YAML (блоковий стиль) | правильно, бо типово yaml |
Третій рядок каже одне: коли вже є -o=yaml, -P не завжди потрібен. Але нешкідливим його теж не назвеш — дивіться дані про час у шостому розділі. Четвертий рядок пояснює, чому «я списав і воно працює»: багато хто подає дані через pipe, і це випадково оминає гілку з розширенням.
3. Пакетна конвертація: три варіанти для shell
Конвертація одного файла — це лише старт. Майже кожна реальна причина робити це в командному рядку — пакетна: усі конфігурації в каталозі, усі збережені знімки API, і все одразу в YAML.
Цикл bash / zsh (найчитабельніший, для одного каталогу):
for f in configs/*.json; do
yq -o=yaml '.' "$f" > "${f%.json}.yaml"
done
find, рекурсивно (коли важливі підкаталоги):
find . -name '*.json' -exec sh -c 'yq -o=yaml "." "$1" > "${1%.json}.yaml"' _ {} \;
PowerShell (на Windows CI):
Get-ChildItem *.json | ForEach-Object { yq -o=yaml '.' $_.FullName > ($_.BaseName + '.yaml') }
У всіх трьох варіантів одна жорстка передумова: спершу переконайтеся, що вихідний JSON валідний. Конвертер — це не парсер. Зіткнувшись із синтаксичною помилкою, багато конвертерів видають розпливчасту позицію замість тієї деталізації по рядках, яку дає валідатор. Додати одну перевірку всередину циклу коштує майже нічого:
for f in configs/*.json; do
jq empty "$f" || { echo "invalid: $f"; continue; }
yq -o=yaml '.' "$f" > "${f%.json}.yaml"
done
jq empty лише парсить і нічого не друкує, а на невалідному JSON повертає ненульовий код виходу. Разом із set -euo pipefail це зупиняє весь конвеєр на першому зіпсованому файлі, а не видає купу напівконвертованих результатів.
4. Чотири параметри виводу — чотири різні задачі
Список параметрів yq довгий, але на шляху JSON → YAML запам'ятати треба лише чотири:
| Параметр | Що робить | Коли обов'язково передавати |
|---|---|---|
-o=yaml / -oy |
Задає формат виводу YAML | Завжди, коли вхід — файл .json |
-P / --prettyPrint |
Примусово блоковий стиль, нормалізує відступи | Читання з stdin або коли важливі відступи послідовностей |
-I=n |
Ширина відступу, типово 2 | Downstream вимагає 4 пробіли (деякі Ansible style guide) |
--split-exp |
Розбиває елементи масиву на документи | Генерація мультиресурсних маніфестів Kubernetes |
Про мультидокумент варто сказати окремо. Маніфести Kubernetes за звичаєм розділяють ресурси через ---. Генеруючи це з JSON-масиву:
yq -P -o=yaml '.[] | splitDoc' --split-exp 'true' kubernetes-list.json
Без --split-exp ви отримаєте одну послідовність YAML; з ним кожен елемент масиву стає окремим документом, розділеним ---. kubectl apply з'їсть обидва варіанти, але читабельність diff відрізняється на порядок.
5. Лапки: шість типів рядків, які треба лапкувати
Виведення типів у YAML неявне. Рядкове значення, схоже на число чи булеве значення, без лапок буде розпізнане як інший тип. Це не теоретичний ризик: виміряно на yq v4.44.6 — усі шість наведених нижче значень отримують лапки автоматично, що доводить реальність помилки парсера:
printf '{"version":"1.0","enabled":"true","port":"8080","empty":"","colon":"host: value","hash":"v # c","unicode":"端口 8080","ts":"2026-11-03"}' | yq -P -o=yaml '.'
version: "1.0"
enabled: "true"
port: "8080"
empty: ""
colon: 'host: value'
hash: 'v # c'
unicode: 端口 8080
ts: "2026-11-03"
Наслідки, крок за кроком:
| Початкове значення (рядок JSON) | Чим стане без лапок | Реальний вплив |
|---|---|---|
"1.0" |
Число з плаваючою комою 1.0 |
Версія стає дробовою; 1.10 перетворюється на 1.1 |
"true" |
Булеве true |
Логіка умов інвертується або типи не збігаються |
"8080" |
Ціле 8080 |
Тип порту змінюється, порівняння рядків провалюється |
"" |
null |
Порожній рядок зникає, downstream отримує None |
"host: value" |
Помилка парсингу або вкладене відображення | Після двокрапки обов'язковий пробіл, інакше синтаксична помилка |
"2026-11-03" |
Об'єкт дати | Десеріалізується в datetime.date, а не в рядок |
Останній пункт найпідступніший. У JSON дата — це просто рядок; після конвертації вона стає типом дати YAML, і під час зворотної конвертації багато реалізацій видають об'єкт дати у форматі ISO замість початкового рядка — узгодженість зворотного перетворення розривається. І PyYAML, і yq додають тут лапки, що рівнозначно визнанню цього ризику реальним.
До речі, рядок unicode: 端口 8080 виводиться без лапок — китайський текст у YAML безпечний. Хвилюватися треба не за нього, а за порядок ключів і точність, про які йдеться далі.
6. Еквіваленти на Python і Node з реальними вимірюваннями часу
yq можна встановити не скрізь: ізольовані від мережі машини, закриті CI-образи, Windows без менеджера пакетів. Еквіваленти на Python і Node існують, і в кожного свої типові значення, готові вкусити. Як обрати один із трьох шляхів під конкретну задачу, є повна таблиця порівняння в виборі JSON-інструментів командного рядка та редакторів; ця стаття зосереджена лише на напрямку JSON → YAML.
Python + PyYAML (три аргументи, жоден не можна пропустити):
import json, yaml
with open("config.json", encoding="utf-8") as f:
data = json.load(f)
with open("config.yaml", "w", encoding="utf-8") as f:
yaml.safe_dump(
data, f,
default_flow_style=False, # блоковий стиль, а не {a: 1, b: 2}
allow_unicode=True, # не екранувати китайське в \u7AEF\u53E3
sort_keys=False, # зберегти початковий порядок ключів JSON
)
Типові значення цих трьох — False / False / True, тобто PyYAML «з коробки» переставляє ключі за алфавітом і перетворює китайський текст на \uXXXX. Виміряний вивід на тому самому вході з типовими значеннями:
apiVersion: v1
data:
LOG_LEVEL: info
colon: 'host: value'
unicode: "\u7AEF\u53E3 8080"
kind: ConfigMap
metadata:
labels:
app: web
name: app-config
Зверніть увагу: kind тепер стоїть після apiVersion і перед metadata. У маніфестах Kubernetes є умовність спільноти: apiVersion має йти перед kind. Перестановка ключів не ламає парсинг, але робить рев'ю болісним.
Node + js-yaml:
const fs = require("fs");
const yaml = require("js-yaml");
const data = JSON.parse(fs.readFileSync("config.json", "utf8"));
fs.writeFileSync(
"config.yaml",
yaml.dump(data, { lineWidth: -1, noRefs: true, sortKeys: false })
);
lineWidth: -1 вимикає згортання довгих рядків (інакше довгі URL розриваються посередині), а noRefs: true забороняє генерацію якорів і псевдонімів — обов'язково, коли споживачем є kubectl або інший інструмент, що не розкриває їх.
Виміряний час (зразок: 2.39 MB JSON, 12000 записів, кожен із вкладеним об'єктом і двома масивами; машина — пісочниця, використана для цієї задачі):
| Реалізація | Час конвертації | Час зворотного парсингу | Узгодженість зворотного перетворення |
|---|---|---|---|
yq -o=yaml v4.44.6 |
0.45 s | 0.45 s | ідентично |
yq -P -o=yaml v4.44.6 |
4.16 s | — | ідентично |
| Node js-yaml 4.x | 0.23 s | 0.11 s | ідентично |
| Python PyYAML 6.0.3 | 3.26 s | 8.01 s | ідентично |
Два неінтуїтивні висновки. По-перше, з -P yq сповільнюється приблизно в 9 разів (0.45 s → 4.16 s), хоча на цьому зразку вивід байт у байт однаковий (2,326,895 байт в обох випадках) — -P робить повний прохід переверстання, і це чимала ціна; якщо конвертуєте сотні файлів, краще цього уникати. По-друге, зворотний парсинг у PyYAML (8.01 s) більш ніж удвічі повільніший за прямий дамп (3.26 s), тож Python — поганий вибір, якщо конвеєр читає YAML назад знову й знову.
І ще одне: 2.39 MB JSON перетворилося на 2.33 MB блокового YAML, тобто стало приблизно на 3% менше. Поширений міф «YAML завжди більший за JSON» не витримує перевірки — при глибокому вкладенні лапки, коми й фігурні дужки JSON можуть важити більше, ніж відступи YAML.
7. Три речі, які не повертаються
Попередні розділи були про те, як конвертувати правильно. Цей — про те, що губиться. Спеціально сконструйований вхід, виміряно:
printf '{"order_id":12345678901234567890,"zip":"0150","ts":"2026-11-03","f":1.0e6}' > t.json
yq -o=yaml '.' t.json
Вивід yq:
order_id: 1.2345678901234567e+19
zip: "0150"
ts: "2026-11-03"
f: 1000000
А після зворотної конвертації: {"order_id":12345678901234567000,"zip":"0150","ts":"2026-11-03","f":1000000}.
20-значний номер замовлення 12345678901234567890 перетворився на 12345678901234567000: останні три цифри обнулилися, і жодного попередження. Причина в тому, що yq йде шляхом парсингу float64 у Go: цілі числа понад 2^53 неможливо подати точно, тому вмикається вивід у науковій нотації, і до моменту зворотної конвертації початкове значення вже втрачене.
Той самий вхід через PyYAML:
order_id: 12345678901234567890
zip: '0150'
ts: '2026-11-03'
f: 1000000.0
Цілі числа в Python мають довільну точність, тож усі 20 цифр зберігаються, і зворотне читання теж дає 12345678901234567890. Якщо у ваших даних є номери замовлень, snowflake id чи ідентифікаційні номери, не використовуйте yq — візьміть Python або спершу перетворіть поле на рядок. Повний механізм цього класу збоїв пояснений у матеріалі про те, чому великі цілі числа в JSON втрачають точність.
Друга втрата — розрізнення float і int: 1.0e6 у виводі yq стає 1000000 (цілим), а PyYAML зберігає 1000000.0. Третя — порядок ключів: PyYAML типово переставляє, yq і js-yaml типово зберігають, і в міжмовній співпраці це приховане джерело розбіжностей.
8. У CI не просто конвертуйте: валідація → конвертація → lint
Вважати конвертацію єдиним кроком — найпоширеніший дефект конвеєра. Нульовий код виходу означає лише «вхідний JSON розпарсився», а не «вивід правильний» і не «вивід задовольняє споживача». Правильна триступенева форма:
#!/usr/bin/env bash
set -euo pipefail
# 1. Валідація джерела: синтаксичний шар
jq empty deployment.json
# 2. Конвертація: явно задаємо формат виводу
yq -o=yaml '.' deployment.json > deployment.yaml
# 3. Валідація артефакту: читається парсером YAML і відповідає правилам
yq -o=json -I=0 '.' deployment.yaml > /dev/null
yamllint deployment.yaml
Той yq -o=json ... > /dev/null у кроці 3 виглядає зайвим, але це найкорисніший рядок усього блоку: він примусово перепарсює артефакт. Помилки відступів, збої розкриття якорів і переписані типи виявляються саме тут. Пропустіть його — і проблема відкладеться до kubectl apply або запуску застосунку.
Якщо артефакт — це конфігурація, яку підтримують люди (Helm values, docker-compose, робочі процеси GitHub Actions), перевірте ще три речі: чи не переставлено ключі, чи не втратили цифри довгі числа і чи потрібно дописувати коментарі вручну — у JSON немає коментарів, тому будь-яку анотацію доведеться додавати після конвертації; жоден конвертер не вигадає їх за вас.
Часті питання
yq -P чи -o=yaml — що саме писати?
-o=yaml визначає, чи є вивід YAML; -P визначає, як цей YAML виглядає. Коли вхід — файл .json, -o=yaml обов'язковий, а -P потрібен лише тоді, коли вам треба примусовий блоковий стиль або відступи послідовностей. Якщо читаєте з stdin без розширення, yq вже типово видає YAML, тож -P самого по собі достатньо.
Чи можна віддати конвертований YAML одразу в Kubernetes?
Синтаксично так, але з трьома умовами: ключі з умовним порядком на кшталт apiVersion не переставлені (PyYAML переставляє типово, задайте sort_keys=False); якорі і псевдоніми не згенеровані (js-yaml: задайте noRefs: true); довгі числа зберегли цифри. Для мультиресурсних маніфестів розбийте на документи, розділені ---, за допомогою --split-exp.
Чому мій вивід — один рядок {a: 1, b: 2}, а не з відступами?
Це flow style. Він валідний, але знецінює саму конвертацію. Використайте -P, щоб примусити блоковий стиль; якщо ви на PyYAML, переконайтеся, що default_flow_style=False (починаючи з PyYAML 5.1 типове значення вже False, у старіших версіях його треба писати явно).
20-значний номер замовлення втратив останні цифри. Чи можна їх відновити?
Ні — можна лише запобігти. yq парсить через float64, тож цілі числа понад 2^53 втрачають точність за конструкцією. Або перейдіть на Python (цілі довільної точності; перевірено, збережено повністю), або зберігайте довгі числа рядками в самому JSON — друге надійніше, бо воно заодно розв'язує таку саму проблему JSON.parse на боці JavaScript.
Онлайн-конвертер чи командний рядок?
Конфігурації з секретами й знімки API з token конвертуйте локальним командним рядком або клієнтським інструментом — ніколи не завантажуйте. Для разових, невеликих даних, які хочеться підтвердити візуально, онлайн-інструмент швидший; сторінка /convert/yaml на цьому сайті працює повністю у вашому браузері, і дані не покидають вашу машину. Для всього пакетного, відтворюваного або прив'язаного до CI використовуйте командний рядок і вписуйте кроки валідації в той самий скрипт.