Конвертер JSON в YAML

Переводит JSON в YAML и объясняет каждые добавленные кавычки — строки, которые без них молча стали бы логическим значением, числом или датой.

Ввод
YAML
name: deploy
country: 'NO'
startsAt: '12:30'
mode: '0755'
version: '1.10'
released: '2024-01-30'
enabled: 'yes'
script: |-
  set -e
  npm run build
  npm test
replicas: 3
tags:
  - web
  - edge
Почему каждая строка в кавычках

Значений в кавычках: 6. Нужны только в YAML 1.1: 4. Переключите схему выше, чтобы увидеть разницу.

  • countryтолько 1.1

    «NO» прочиталось бы как логическое false.

  • startsAtтолько 1.1

    «12:30» прочиталось бы по основанию 60 как 750.

  • mode

    У «0755» ведущий ноль, поэтому оно прочиталось бы как число 493.

  • version

    «1.10» прочиталось бы как число 1.1.

  • releasedтолько 1.1

    «2024-01-30» прочиталось бы как дата, а не как текст.

  • enabledтолько 1.1

    «yes» прочиталось бы как логическое true.

Что делает этот инструмент

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

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

Почему конвертеру вообще нужны мнения

JSON говорит, какого типа всё. У строки есть кавычки, у числа нет, и третьей возможности не существует. YAML же определяет тип некавыченного значения по его виду: совпало с образцом логического — значит логическое, совпало с числом — значит число, и лишь если не совпало ни с чем, остаётся текстом.

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

Норвежская проблема и её родня

Самый известный случай — список кодов стран. Норвегия — это NO, а в YAML 1.1 некавыченный токен NO есть логическое false. Файл настроек, перечисляющий страны, теряет Норвегию и приобретает false, и нигде ничто не сообщает об ошибке.

Это не одно странное правило, а целое семейство. YAML 1.1 читает y, Y, yes, no, on и off как логические значения в любом написании, и тем ловит химический символ, положение переключателя и ответ на вопрос. А числовые разрешители ещё чуднее:

  • 12:30 — это 750. YAML 1.1 читает разделённые двоеточиями цифры по основанию 60, так что время суток или длительность становятся целым числом.
  • 0755 — это 493. Ведущий ноль означает восьмеричное в YAML 1.1, а в YAML 1.2 тот же текст — десятичное 755: версии расходятся в том, какое число, а не в том, число ли это.
  • 1.10 — это 1.1. Двухчастный номер версии есть число с плавающей точкой, и завершающий ноль исчезает. Зависимость, прикреплённая к 1.10, теперь указывает на 1.1.
  • 2024-01-30 — объект даты, а не строка, потому что у YAML 1.1 есть тип метки времени.
  • Пустая строка — это null, как и голые слова null, Null, NULL и тильда.

Ничто из этого не ошибка YAML. Это разрешители делают ровно то, что обещают, над текстом, который случайно совпал. Единственная защита — заключать в кавычки всё совпадающее, что этот инструмент и делает, а панель находок отчитывается за это построчно.

Две версии, и почему по умолчанию старшая

YAML 1.2 вышел в 2009 году и убрал большинство неожиданных разрешителей. Его базовая схема оставляет логическими только true и false, полностью отказывается от основания 60 и не имеет типа метки времени. Под 1.2 NO, 12:30 и 2024-01-30 — просто строки.

Загвоздка в том, что именно читает ваш файл. PyYAML реализует YAML 1.1, а PyYAML стоит за огромным количеством инструментов — Ansible, старые клиенты Kubernetes, бесчисленные сценарии. Go’шный yaml.v3 и нынешний js-yaml следуют 1.2. Так что один и тот же документ читается двумя разными способами в зависимости от того, кто его открыл, и единственный безопасный везде вывод — закавыченный под 1.1.

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

Строки, ломающие синтаксис, а не тип

Вторую группу строк приходится закавычивать по другой причине: не потому что YAML прочёл бы их как иной тип, а потому что они вовсе не разобрались бы как текст.

  • Двоеточие с последующим пробелом заканчивает ключ. «note: time: now» прочиталось бы как ключ note, значением которого является ключ time.
  • Пробел с последующей решёткой открывает комментарий, и всё, что за ним, исчезает.
  • Начальные -, ?, :, [, ], {, }, #, &, *, !, |, >, %, @ или обратный апостроф — символы-индикаторы и означают нечто структурное.
  • Ведущий или замыкающий пробел некавыченное значение не сохраняет, поэтому « x » возвращается как «x».
  • Табуляция где угодно внутри значения отвергается начисто: PyYAML отказывает всему документу вместо того, чтобы прочесть его неверно, так что этот случай падает громко.

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

Многострочные строки и указатель обрезки

Строка с переводами строки — сценарий, сертификат, кусок прозы — обычно и есть причина, по которой YAML вообще понадобился. JSON способен записать её лишь экранированиями обратный-слэш-n в одной очень длинной строке; у YAML есть литеральный блочный скаляр, открываемый вертикальной чертой, под которой текст стоит с отступом и читаемо.

Тонкость в том, что происходит с завершающими переводами строки, и этим управляет указатель обрезки:

  • Голая вертикальная черта подрезает: сколько бы завершающих переводов у блока ни было, значение получает ровно один.
  • Вертикальная черта с минусом срезает: значение не получает ни одного.
  • Вертикальная черта с плюсом сохраняет: значение получает их все.

Этот инструмент выбирает указатель по той строке, которую получил, так что значение переживает путь туда и обратно в точности. Знать об этом стоит потому, что умолчание — голая черта — это то, что пишут от руки, и оно молча нормализует строку, оканчивавшуюся двумя переводами или ни одним.

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

Больше одного документа

У YAML есть то, чего нет у JSON: файл может содержать поток документов, разделённых тремя дефисами. Это формат манифеста Kubernetes, и потому массив JSON так часто должен стать чем-то иным, нежели последовательность YAML.

Переключатель здесь выводит каждый элемент массива верхнего уровня отдельным документом. А когда ввод вовсе не одно значение JSON, но каждая строка разбирается сама по себе, он читается как NDJSON — построчный формат, в котором приходят журналы и выгрузки API, — и каждая строка становится документом. Такое прочтение есть догадка, поэтому о нём сообщается над выводом, а не делается молча.

Откат применяется лишь когда весь ввод не разбирается, а каждая строка разбирается, чего просто испорченный документ не сделает. Синтаксическая ошибка по-прежнему всплывает как синтаксическая ошибка, со строкой и столбцом, где произошла.

Чего преобразование сохранить не может

Две вещи теряются ещё до того, как инструмент увидит ваши данные, обе в самом разборе JSON, и стоит знать какие.

Повторяющиеся ключи. JSON позволяет объекту дважды перечислить один ключ, и большинство разборщиков молча оставляет последний. YAML запрещает дубликаты вовсе, так что вывод окажется корректным, но прежнее значение уже потеряно — и никакой конвертер не сообщит о том, чего никогда не получал.

Точность целых. Число JSON больше примерно девяти квадриллионов не переживает разбор в double, поэтому идентификатор вроде 12345678901234567890 возвращается округлённым. Это не беда YAML и не беда, вносимая этим инструментом; так происходит в любом разборщике JSON в языке. Если большой идентификатор важен, его место в строке по обе стороны.

Замечания о выводе

Отступ делается пробелами, всегда, потому что YAML запрещает табуляции для отступа полностью — это одна из немногих вещей, в которых формат строг. Два пробела — общепринято; четыре предлагаются потому, что некоторые внутренние стили их требуют.

Последовательности идут с отступом под своим ключом. И эта форма, и форма без отступа — корректный YAML и значат одно и то же; отступную пишет большинство людей, и её же правильно сворачивает большинство редакторов.

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

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

Частые вопросы

Является ли JSON уже корректным YAML?
Под YAML 1.2 — да: спецификация говорит об этом прямо, и разборщик 1.2 прочтёт файл JSON напрямую. На практике пользы немного, потому что причина преобразовывать — читаемость: комментарии, блочные скаляры, отсутствие фигурных скобок, — а вставка JSON в файл YAML не даёт ничего из этого. Под YAML 1.1 это не вполне верно, что ещё одна причина различать версии.
Почему моей строке достались кавычки, которые ей будто бы не нужны?
Почти наверняка нужны. YAML типизирует некавыченное значение сопоставлением образцов, так что NO, yes, off, 12:30, 0755, 1.10, 2024-01-30 и пустая строка — все перестают быть строками. Панель находок называет каждый случай и показывает значение, которым он стал бы, чтобы утверждение можно было проверить, а не принять на веру. Если вы целитесь в разборщик YAML 1.2, смена схемы уберёт те, что нужны только 1.1.
В чём здесь разница между YAML 1.1 и 1.2?
1.2 отказался от разрешителей, вызывающих большинство неожиданностей: yes/no/on/off больше не логические, основание 60 исчезло, а типа метки времени нет. PyYAML реализует 1.1 и по-прежнему повсюду, поэтому осторожный вывод стоит по умолчанию; настройка 1.2 нужна тогда, когда вы знаете, что будет читать файл.
Может ли он перевести YAML обратно в JSON?
Нет, намеренно. Читателю YAML нужны якоря, псевдонимы, теги, ключи слияния, пять стилей скаляров и две версии схемы, а тонко ошибиться в любом из них — значит принять файл и вернуть данные, отличные от тех, что в нём были. Такой отказ беззвучен, и это делает его хуже, чем отсутствие возможности вовсе.
Как получить многодокументный файл в духе Kubernetes?
Включите переключатель, который выводит каждый элемент массива верхнего уровня отдельным документом, и массив станет документами, разделёнными тремя дефисами. Если же ваш ввод — NDJSON, по одному объекту JSON в строке, как часто приходят журналы и выгрузки API, это распознаётся само и сообщается над выводом.
Почему в выводе нет комментариев?
Потому что их не было во вводе. Комментарии — главное, что есть у YAML и нет у JSON, а конвертер не может их выдумать. Это стоит помнить и в обратную сторону: если прогнать файл YAML через JSON и назад, всякий комментарий в нём исчезнет.
Отправляется ли что-нибудь из вставленного на сервер?
Нет. Разбор и преобразование целиком выполняются в вашем браузере; ничто не загружается и не записывается, и всё работает без сети.