Правила отображения XML→JSON: Атрибуты, #text и обработка массивов
Вы конвертируете XML в JSON, и результат не соответствует вашим ожиданиям: атрибуты превращаются в @attr, текстовый контент становится #text, а иногда появляются массивы там, где вы ожидали объекты. Понимание правил отображения XML↔JSON помогает предсказать результат конвертации и правильно структурировать данные для обратимых преобразований.
Фундаментальная проблема: XML ≠ JSON
XML и JSON имеют разные модели данных:
- XML: Элементы могут иметь атрибуты, текстовый контент и дочерние элементы одновременно
- JSON: Объекты имеют пары ключ-значение; нет различия между атрибутами и дочерними элементами
Не существует единственно "правильного" способа конвертации между ними, но появились общие соглашения.
Правило 1: Элементы становятся объектами
Простые XML элементы преобразуются в JSON объекты:
<!-- XML -->
<person>
<name>Alice</name>
<age>30</age>
</person>{
"person": {
"name": "Alice",
"age": "30"
}
}Правило 2: Атрибуты получают специальный префикс
XML атрибуты обычно становятся JSON ключами с префиксом @:
<!-- XML -->
<person id="123" active="true">
<name>Alice</name>
</person>{
"person": {
"@id": "123",
"@active": "true",
"name": "Alice"
}
}Префикс @ отличает атрибуты от дочерних элементов. Без него вы не могли бы отличить <person id="123"> от <person><id>123</id></person>.
Правило 3: Текстовый контент становится #text
Когда элемент имеет и атрибуты, и текстовый контент, текст становится специальным ключом #text:
<!-- XML -->
<price currency="USD">49.99</price>{
"price": {
"@currency": "USD",
"#text": "49.99"
}
}Без ключа #text не было бы способа представить и атрибут currency, и текстовое значение 49.99.
Простой текст (без атрибутов) = прямое значение
Если элемент содержит только текстовый контент (без атрибутов, без дочерних элементов), он становится простой строкой:
<!-- XML -->
<name>Alice</name>{
"name": "Alice"
}Обёртка #text не нужна, потому что нет неоднозначности.
Правило 4: Повторяющиеся элементы становятся массивами
Несколько элементов с одинаковым именем преобразуются в JSON массив:
<!-- XML -->
<items>
<item>Widget A</item>
<item>Widget B</item>
<item>Widget C</item>
</items>{
"items": {
"item": [
"Widget A",
"Widget B",
"Widget C"
]
}
}Единичный элемент ≠ массив
Это распространённая ловушка. Единичный элемент не становится массивом:
<!-- XML -->
<items>
<item>Widget A</item>
</items>{
"items": {
"item": "Widget A" // Строка, не массив!
}
}Это означает, что ваш код должен проверять, является ли item строкой или массивом. Некоторые конвертеры имеют опцию "всегда использовать массивы", чтобы избежать этой несогласованности.
Правило 5: Пустые элементы становятся пустыми объектами или null
Пустые XML элементы обычно становятся пустыми объектами или null:
<!-- XML -->
<data>
<empty></empty>
<selfClosing/>
</data>{
"data": {
"empty": {},
"selfClosing": {}
}
}Или, в зависимости от конвертера:
{
"data": {
"empty": null,
"selfClosing": null
}
}Реальный пример: Ответ API
Вот реалистичный ответ SOAP API и его JSON конвертация:
XML ответ API
<?xml version="1.0" encoding="UTF-8"?>
<response status="success" timestamp="2026-01-17T10:30:00Z">
<user id="12345" verified="true">
<name>Alice Johnson</name>
<email>alice@example.com</email>
<roles>
<role priority="high">admin</role>
<role priority="normal">user</role>
</roles>
<metadata>
<created>2020-01-15</created>
<lastLogin>2026-01-16T08:00:00Z</lastLogin>
</metadata>
</user>
</response>JSON конвертация
{
"response": {
"@status": "success",
"@timestamp": "2026-01-17T10:30:00Z",
"user": {
"@id": "12345",
"@verified": "true",
"name": "Alice Johnson",
"email": "alice@example.com",
"roles": {
"role": [
{
"@priority": "high",
"#text": "admin"
},
{
"@priority": "normal",
"#text": "user"
}
]
},
"metadata": {
"created": "2020-01-15",
"lastLogin": "2026-01-16T08:00:00Z"
}
}
}
}Ключевые наблюдения:
- Корневые атрибуты
statusиtimestampстановятся@statusи@timestamp - Атрибуты пользователя
idиverifiedстановятся@idи@verified - Простые элементы как
nameиemailстановятся обычными строками - Повторяющиеся элементы
roleстановятся массивом - Каждый
roleимеет атрибут и текстовый контент, поэтому он становится объектом с@priorityи#text
Обратная конвертация JSON в XML
Обратная конвертация (JSON → XML) следует тем же правилам, но требует внимательной обработки:
{
"order": {
"@id": "ORD-001",
"customer": "Alice",
"items": {
"item": [
"Widget A",
"Widget B"
]
},
"total": {
"@currency": "USD",
"#text": "99.99"
}
}
}Преобразуется в:
<order id="ORD-001">
<customer>Alice</customer>
<items>
<item>Widget A</item>
<item>Widget B</item>
</items>
<total currency="USD">99.99</total>
</order>Применённые правила:
- Ключи, начинающиеся с
@, становятся атрибутами #textстановится текстовым контентом- Массивы становятся повторяющимися элементами
- Простые строковые значения становятся текстовым контентом
Пространства имён: Усложнение
XML пространства имён добавляют сложность в JSON конвертацию:
<!-- XML с пространством имён -->
<order xmlns="http://example.com/orders" xmlns:cust="http://example.com/customer">
<cust:customer id="123">
<cust:name>Alice</cust:name>
</cust:customer>
<item>Widget</item>
</order>Разные конвертеры обрабатывают это по-разному:
Вариант 1: Сохранение префиксов
{
"order": {
"@xmlns": "http://example.com/orders",
"@xmlns:cust": "http://example.com/customer",
"cust:customer": {
"@id": "123",
"cust:name": "Alice"
},
"item": "Widget"
}
}Вариант 2: Расширение пространств имён
{
"{http://example.com/orders}order": {
"{http://example.com/customer}customer": {
"@id": "123",
"{http://example.com/customer}name": "Alice"
},
"{http://example.com/orders}item": "Widget"
}
}Используйте XML ↔ JSON конвертер, чтобы протестировать, как ваш XML с пространствами имён конвертируется.
Смешанный контент: Граничный случай
Смешанный контент (текст и элементы смешаны вместе) является валидным XML, но неудобен в JSON:
<!-- XML со смешанным контентом -->
<description>
Цена составляет <price currency="USD">49.99</price> только для новых клиентов.
</description>Это не может быть чисто представлено в JSON, потому что JSON объекты не сохраняют порядок и не позволяют текст между ключами. Конвертеры обычно обрабатывают это, создавая массив:
{
"description": [
"Цена составляет ",
{
"price": {
"@currency": "USD",
"#text": "49.99"
}
},
" только для новых клиентов."
]
}Смешанный контент редко встречается в XML, ориентированном на данные (как API), но часто в XML, ориентированном на документы (как HTML или DocBook).
Секции CDATA
Секции CDATA сохраняют специальные символы без экранирования:
<!-- XML -->
<code><![CDATA[
if (x < 10 && y > 5) {
console.log("Hello");
}
]]></code>В JSON, CDATA становится обычным текстом:
{
"code": "\n if (x < 10 && y > 5) {\n console.log(\"Hello\");\n }\n"
}Обёртка CDATA теряется, но контент сохраняется с правильным экранированием.
Комментарии теряются
XML комментарии не преобразуются в JSON (в JSON нет синтаксиса комментариев):
<!-- XML -->
<config>
<!-- Это важно -->
<setting>value</setting>
</config>{
"config": {
"setting": "value"
}
}Комментарии молча отбрасываются во время конвертации.
Реальный сценарий отладки
Вы получаете XML из SOAP API и нужно работать с ним в JavaScript. Вот процесс:
1. Конвертировать XML в JSON
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetUserResponse xmlns="http://api.example.com">
<user id="12345">
<name>Alice</name>
<roles>
<role>admin</role>
<role>user</role>
</roles>
</user>
</GetUserResponse>
</soap:Body>
</soap:Envelope>Вставьте в XML ↔ JSON конвертер
2. Понять структуру JSON
{
"soap:Envelope": {
"@xmlns:soap": "http://schemas.xmlsoap.org/soap/envelope/",
"soap:Body": {
"GetUserResponse": {
"@xmlns": "http://api.example.com",
"user": {
"@id": "12345",
"name": "Alice",
"roles": {
"role": ["admin", "user"]
}
}
}
}
}
}3. Доступ к данным в JavaScript
const envelope = JSON.parse(jsonString);
const user = envelope["soap:Envelope"]["soap:Body"].GetUserResponse.user;
console.log(user["@id"]); // "12345"
console.log(user.name); // "Alice"
console.log(user.roles.role); // ["admin", "user"]
// Проверка, является ли role массивом (обработка одного vs нескольких ролей)
const roles = Array.isArray(user.roles.role)
? user.roles.role
: [user.roles.role];
roles.forEach(role => console.log(role));Распространённые ловушки и решения
Ловушка 1: Один vs несколько элементов
Проблема: Код ломается, когда в XML один <item> вместо нескольких
Решение: Всегда проверяйте, является ли значение массивом
// Защитный парсинг
const items = data.items.item;
const itemArray = Array.isArray(items) ? items : [items];
itemArray.forEach(item => {
console.log(item);
});Ловушка 2: Доступ к атрибутам
Проблема: Забывание префикса @ для атрибутов
Решение: Помните, что атрибуты имеют префикс @
// Неправильно
console.log(user.id); // undefined
// Правильно
console.log(user["@id"]); // "12345"
// Или используйте скобочную нотацию
console.log(user["@id"]);Ловушка 3: Текст + атрибуты
Проблема: Ожидание простого значения, когда элемент имеет атрибуты
<price currency="USD">49.99</price>// Неправильно - price это объект, а не строка
console.log(data.price); // { "@currency": "USD", "#text": "49.99" }
// Правильно
console.log(data.price["#text"]); // "49.99"
console.log(data.price["@currency"]); // "USD"Инструменты для тестирования конвертации
Используйте XML ↔ JSON конвертер, чтобы:
- Тестировать конвертацию XML → JSON с реальными данными
- Увидеть, как обрабатываются атрибуты, массивы и пространства имён
- Проверить обратимую конвертацию (XML → JSON → XML)
- Отлаживать структуры ответов API
Краткая справка: Правила отображения
| Паттерн XML | Результат JSON | Примечания |
|---|---|---|
<name>Alice</name> | {"name": "Alice"} | Простой элемент → строка |
<user id="123"> | {"@id": "123"} | Атрибут → @ключ |
<price currency="USD">49.99</price> | {"@currency": "USD", "#text": "49.99"} | Атрибут + текст |
<item>A</item><item>B</item> | {"item": ["A", "B"]} | Повторяющиеся → массив |
<item>A</item> (один) | {"item": "A"} | Единичный → не массив |
<empty/> | {"empty": {}} или null | Пустой элемент |
<!-- комментарий --> | Пропущен | Нет эквивалента в JSON |
<![CDATA[...]]> | Обычный текст (экранирован) | Обёртка CDATA потеряна |
<ns:element> | {"ns:element": ...} | Префикс пространства имён сохранён |
Лучшие практики
- Тестируйте конвертации - Используйте инструменты конвертации с реальными данными перед написанием кода
- Обрабатывайте массивы защитно - Всегда проверяйте, является ли значение массивом или одним элементом
- Помните префиксы -
@для атрибутов,#textдля смешанного контента - Документируйте структуру - Отмечайте, какие поля являются массивами, какие атрибутами
- Избегайте смешанного контента - Проектируйте XML, ориентированный на данные, без текста между элементами
- Рассмотрите схемы - XSD схемы помогают предсказать массив vs единичный элемент
Резюме
Конвертация XML в JSON следует предсказуемым правилам:
- Элементы → объекты, атрибуты →
@ключ - Текст с атрибутами → ключ
#text - Повторяющиеся элементы → массивы (но единичный элемент остаётся единичным)
- Пустые элементы → пустые объекты или null
- Пространства имён сохраняются как префиксы или расширенные URI
- Комментарии и секции CDATA теряются/сплющиваются
Понимание этих правил помогает предсказать результат конвертации и писать надёжный код парсинга. Используйте XML ↔ JSON конвертер, чтобы тестировать конвертации с вашими реальными данными.
Нужно конвертировать между XML и JSON? Используйте XML ↔ JSON конвертер для тестирования конвертаций и понимания, как будут структурированы ваши данные. Также посмотрите JSON форматтер для очистки преобразованного JSON.