XML: как проверить на ошибки и превратить в JSON

К XML приходят по разным причинам. Выгрузка из учётной системы, sitemap для поисковика, RSS-лента, конфигурация сборки, ответ веб-сервиса: всё это приходит в XML, хотя половина приложений ждёт JSON. Дальше начинается неприятная часть: файл не открывается, потому что в нём один незакрытый тег, а в логе об этом нет ни слова. Разберёмся, как устроены оба формата, как найти ошибку в XML за десять секунд и как перевести одно в другое, не потеряв данные по дороге.
Чем XML отличается от JSON
XML это язык разметки: текст, в котором смысл несут не сами слова, а названия тегов. JSON это формат данных: структура из ключей и значений, где тип каждого значения известен заранее. Сравните два описания одной и той же записи.
XML:
<person><name>Иван</name><age>34</age></person>
JSON:
{"name":"Иван","age":34}Разница видна сразу. В XML возраст это текст «34», и пока не сказано, что это число, оно текстом и останется. В JSON 34 это число, и его можно складывать. В XML есть атрибуты, которые не содержат значения, а только свойства, в JSON для свойств используются отдельные ключи. В XML нет встроенных типов true, false и null: логическое значение или пустоту приходится описывать самому или задавать в схеме, тогда как в JSON эти типы есть по определению.
- XML строгий: имя тега, регистр символов, кавычки вокруг значений атрибутов проверяются.
- JSON строг к записи, но снисходителен к смыслу: только двойные кавычки, никаких комментариев и никакой запятой в конце.
- В XML есть комментарии, XML-декларация, инструкции обработки и секции CDATA. В JSON ничего из этого нет.
- В XML повторяющийся элемент просто повторяется, а в JSON одинаковые элементы собираются в массив.
- В XML есть пространства имён, и одно и то же имя может принадлежать разным словарям. В JSON такой проблемы нет.
Когда применять XML, а когда JSON
Выбор на практике сводится к тому, кто первый задал формат. Если формат выбираете вы, берите JSON: он компактнее, разбирается без предварительной настройки и одинаково понимается в браузере, в мобильном приложении и в серверном коде.
- JSON, когда данные идут между вашим кодом и браузером или мобильным приложением.
- JSON, когда ответ отдаётся как API и важна скорость разбора.
- XML, когда формат задан извне: sitemap, RSS, выгрузка, отчёт, документ, который подписывается электронной подписью.
- XML, когда нужны комментарии, порядок элементов или привязка к схеме XSD.
- Если данные приходят таблицей, а не деревом, сначала доведите их до JSON по инструкции как перевести CSV в JSON, а дальше работайте уже с объектом.
Проверка XML на ошибки: что ищет валидатор
Ошибка в XML почти никогда не выглядит как сообщение компилятора. Обычно это фрагмент где-то в середине большого файла, где потерялась закрывающая скобка или в атрибуте стоит непарная кавычка. Проверка сводится к тому, чтобы пройти файл тегами и убедиться, что они закрываются в правильном порядке.
- Незакрытый тег: <a><b>текст</a>. Закрывающий тег обязан совпадать с последним открытым.
- Лишний закрывающий тег: </div> без парного <div>.
- Кавычки в атрибутах: <a href=page.html>. В XML кавычки обязательны, а внутри значения не может стоять символ <.
- Неэкранированный амперсанд: <p>Цена & доставка</p>. Символ & в XML всегда начинает сущность, поэтому его пишут как &.
- Отсутствующий слеш у одиночного элемента: <br> вместо <br/>.
- Пустой документ или текст вне корневого элемента.
<catalog>
<book id="1">
<title>Дроны</title>
<price currency="RUB">1990</price>
</book>
<book id="2">
<title>Верёвки</title>
</catalog>Что здесь не так: тег <book id="2"> открыт, но закрыт не был, а </catalog> закрыл его вместо этого. Правильный вариант ставит </book> перед </catalog>. Форматтер ловит это и указывает на неожиданный закрывающий тег. Делается проверка так: файл вставляется в форматтер XML и нажимается Format. Разбор парности тегов занимает секунды, и ошибиться в нём трудно.
Что такая проверка ловит, а что нет
Здесь важно не переоценить возможности. Проверка парности тегов находит незакрытый и лишний закрывающий тег, но вот кавычки в атрибутах, неэкранированный амперсанд и опечатку в имени атрибута она пропустит: структура от этого не рассыпается. Для настоящей проверки существуют схемы XSD и DTD, и это уже работа настоящего валидатора, а не простого форматтера.
Второе ограничение связано с тем, что проверка идёт по тегам, а не по содержимому. Опечатку в тексте форматтер не заметит, потому что текст для него просто символы. Зато вложенность вроде <b><i></b></i> он посчитает ошибочной, и это правильно: элементы должны закрываться в обратном порядке, как стек.
Namespace и префиксы
Пространства имён нужны, чтобы два разных словаря в одном документе не сливались. Типичный пример: RSS-лента использует пространство имён для стандартных элементов, а внутри лежит Atom-вставка или медиа-расширение, и элементы с одинаковым именем из разных словарей должны оставаться разными.
<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:media="http://search.yahoo.com/mrss/">
<title>Новости</title>
<media:thumbnail url="pic.jpg"/>
</feed>Атрибут xmlns задаёт пространство имён по умолчанию: все элементы без префикса принадлежат этому словарю. Атрибут xmlns:media объявляет префикс media и привязывает его к другому адресу. Сам адрес в данных не появляется, он работает как метка: важно только, чтобы он совпадал в объявлении и в использовании. Правило простое, если префикс встречается в документе, он обязан быть объявлен, иначе документ невалиден.
Разница с JSON заметна. В JSON нет ни префиксов, ни пространств имён, и ключ title всегда значит ровно одно и то же. В XML имя элемента это комбинация локального имени и пространства имён, и именно поэтому при конвертации в JSON префиксы приходится обрабатывать отдельно.
Документ или фрагмент
Полноценный XML-документ начинается с необязательной, но очень желательной XML-декларации, в которой указывается версия и кодировка.
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<item id="7">Верёвки</item>
</catalog>Правила такие: у документа ровно один корневой элемент, все остальные находятся внутри него, а декларация стоит первой, без пробелов и без невидимого символа перед ней. Фрагмент, вырезанный из документа, декларации не содержит и корневого элемента может не иметь. Многие парсеры принимают фрагмент, но не примут документ без единственного корня.
Кодировку лучше указывать явно. Файл может выглядеть нормально в редакторе и посыпаться при разборе, если декларация говорит про UTF-8, а байты лежат в windows-1251. Формат XML 1.0 был опубликован как рекомендация W3C в 1998 году, и требование объявлять кодировку в декларации с тех пор не менялось.
Как превратить XML в JSON: правила и что теряется
Готового преобразования одно в другое не существует, поэтому приходится договариваться о правилах. Рабочая схема выглядит так: имя элемента становится ключом, текст внутри становится значением, повторяющиеся элементы собираются в массив, а атрибуты уезжают в отдельный объект с ключом @attributes.
<item id="7" stock="3">
<name>Верёвки</name>
<price currency="RUB">1990</price>
<tag>туризм</tag>
<tag>вязка</tag>
</item>
JSON:
{
"@attributes": { "id": "7", "stock": "3" },
"name": "Верёвки",
"price": { "@attributes": { "currency": "RUB" }, "#text": "1990" },
"tag": ["туризм", "вязка"]
}Заметьте две особенности. Первая: числа остаются строками, потому что в XML «1990» это текст, и объявить его числом можно только по внешнему правилу, например по схеме. Вторая: одиночный тег tag превратился в строку, а два одинаковых тега собрались в массив. Из-за этого структура зависит от данных, и потребителю JSON приходится обрабатывать оба варианта.
Что теряется при конвертации
- Порядок элементов, если он не отражался в именах ключей. В JSON объект не упорядочен, и последовательность «сначала цена, потом название» нигде не сохранится.
- Смешанное содержимое: <p>Текст <b>жирный</b> продолжение</p> превращается в нечто невнятное, потому что у элемента появляется смесь текста и вложенного узла.
- Префиксы пространств имён, если их не разобрать заранее. Ключ media:thumbnail в JSON выглядит странно, и разумнее заменить его на thumbnail с отдельной пометкой об источнике.
- Различие между элементом без атрибутов и элементом с пустым значением: <a/> и <a></a> в JSON превращаются в null и в пустую строку, а это разные вещи.
- Комментарии и XML-декларация, если не вынести их отдельно.
Порядок и смешанное содержимое обычно либо оставляют как есть, если потребителю это не нужно, либо обрабатывают вручную. В выгрузках порядок элементов часто значим, и там приходится заранее договариваться о схеме JSON, а не полагаться на автоматический разбор.
Обратно: JSON в XML
Обратное преобразование проще, потому что JSON устроены как дерево ключей, а ключи прекрасно превращаются в имена тегов. Правила зеркальные.
{ "name": "Верёвки", "tags": ["туризм", "вязка"] }
<root>
<name>Верёвки</name>
<tags>туризм</tags>
<tags>вязка</tags>
</root>Тонкость в том, что массив в JSON не имеет имени. Пока ключ tags становится корневым тегом, всё выглядит естественно, но если на верхнем уровне окажется массив без объекта-обёртки, разбирать его будет нечем. Поэтому JSON для XML обычно заворачивают в объект с именем корня. И ещё: имя ключа должно быть корректным XML-именем, без пробелов и обязательно с буквы или подчёркивания на первом месте.
Проверить результат можно в форматтере JSON: он показывает ошибку с указанием строки и столбца, если структура нарушена, и строит дерево по данным. Подробнее о том, как проверять JSON и не сломать его при правке, написано в статье JSON: как проверить и отформатировать.
Типичные ошибки
- Закрыли тег не в том порядке, в каком открыли: <a><b></a></b>. Закрывать нужно в обратном порядке, как стек.
- Забыли экранировать амперсанд в ссылке: параметр a=1&b=2 внутри атрибута приведёт к ошибке, нужно &.
- Скопировали XML из выгрузки, где разделителем служит & вместо &.
- Поверили, что JSON разрешает комментарии. Не разрешает, не пропускает одинарные кавычки и не терпит висячую запятую.
- Считают, что при конвертации в JSON числа останутся числами. Без схемы они станут строками.
- Объявили префикс в одном файле и забыли про него в другом. Каждый XML-документ самодостаточен: объявление пространства имён из соседнего файла не переносится.
Частые вопросы
Чем XML лучше JSON?
Возможностей у XML больше: комментарии, инструкции обработки, секции CDATA, пространства имён, привязка к схеме XSD, электронная подпись. Поэтому там, где формат задан извне или требуется юридическая значимость документа, XML остаётся стандартом. Но разбирать его дороже, а объём больше, поэтому внутри приложений обычно побеждает JSON.
Валидатор пишет, что XML валиден, а программа его не читает. Что делать?
Проверьте три вещи, которые настоящие парсеры ловят строже, чем простой счётчик тегов. Первое: кодировка. Объявление UTF-8 при фактических байтах в windows-1251 даёт битые символы, а иногда и ошибку разбора. Второе: XML-декларация не должна быть ничем предшествована, никакого пробела и никакого невидимого символа в начале файла. Третье: в значениях атрибутов не должно быть символа <, а амперсанд должен быть экранирован.
Что такое CDATA и когда это нужно?
Это секция, внутри которой содержимое не разбирается как разметка: <![CDATA[ любой текст, даже с тегами <b> ]]>. Ею пользуются, когда в данные попадает чужой HTML или кусок кода. Ограничений два: последовательность ]]> внутри секции недопустима, и вложенная секция внутри CDATA тоже не работает, потому что закрывающая пара уже использована.
Можно ли сжать XML?
Да, и это отдельный режим работы. Убираются пробелы между тегами и переносы строк, а содержательный текст сохраняется. Сжатый XML плохо читается, поэтому его используют для передачи по сети, а для чтения оставляют обычный отформатированный вид. В минификаторе сжатие доступно для JavaScript, CSS и HTML, а для XML отдельная кнопка есть в форматтере.
Что такое XSD и зачем он, если есть JSON Schema?
XSD это схема, описывающая структуру XML-документа: какие элементы допустимы, в каком порядке, какие у них типы. Проверка по XSD даёт строгий ответ, что документ соответствует ожиданиям, вплоть до значений и ограничений. JSON Schema делает то же самое для JSON, и в валидаторе JSON Schema можно проверить данные по готовой схеме. Схемы нужны тогда, когда с документом работает не только ваш код.
Форматирование XML
Форматирование, украшение, минификация и валидация XML документов
Форматирование и валидация JSON
Форматирование, украшение, валидация и минификация JSON с древовидным просмотром
Минификатор JS / CSS / HTML
Минификация JavaScript, CSS и HTML для уменьшения размера файлов со статистикой сжатия
Валидатор JSON Schema
Валидация JSON данных по JSON Schema с подробными сообщениями об ошибках и поддержкой draft-07