← назад к разделу

В дефекте написано: «скидка не приходит». В ответе поле discount со значением null; разработчик отвечает «всё верно, скидки нет» и закрывает. Через неделю та же проверка краснеет на другом стенде, а там поля discount нет вообще: ответы разные, описаны одинаково.

Данные приезжают в четырёх упаковках: JSON из API, XML из старых обменов, YAML в настройках стенда, CSV в выгрузке. Смысл один, правила чтения разные — и часть промахов растёт не из логики приложения, а из того, как прочитали упаковку.

один и тот же заказ — четыре упаковки JSON{"id": 42, "total": 4990, "paid": true, "promo": null} XML<order id="42"> <total>4990</total> <paid>true</paid></order> YAMLid: 42total: 4990paid: truepromo: ~ CSVid;total;paid;promo42;4990;true;последнее поле пустое —видно только по числу «;» данные одни — наборов правил чтения четыре

Заказ 42 на 4990 рублей, оплаченный, без промокода — в четырёх форматах. Смысл везде один, но в JSON у значения есть тип, в XML номер уехал в атрибут, в YAML пустое значение записано как «~», а в CSV его вообще не видно — про него говорит лишний разделитель в конце строки.

JSON: типы значений и две пустоты

Ответ API выглядит как текст, а проверять надо по значениям — и решают часто кавычки.

Видов значений шесть: объект в фигурных скобках, массив в квадратных, строка в кавычках, число без кавычек, true/false и null. Одинарные кавычки и запятая после последнего поля запрещены. Разница между "42" и 42 не косметическая: строка сортируется по алфавиту ("10" раньше "9") и на приёме часто отвергается. Типов для даты и денег нет: дату шлют строкой "2026-03-01T10:00:00Z", сумму — числом, а рубли это или копейки — сказано в описании сервиса.

Вложенность даёт путь к значению: order.customer.name, у массива в пути появляется номер — и если порядок сервис не обещал, проверка «первый элемент — тот самый» будет плавать.

Пустота бывает двух сортов, и это разные истории.

живой пример

import json

ответ = json.loads('{"id": 42, "discount": null}')
print("discount" in ответ, ответ.get("discount"))
print("comment" in ответ, ответ.get("comment"))
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Неделя бесплатно →

Выведет True None и False None. Поле discount есть и пусто — скидку посчитали и получили «ничего». Поля comment нет вовсе: другая версия, не хватило прав, не заполнено. Обращение по ключу даёт в обоих случаях одно и то же, поэтому разницу легко потерять — в баг-репорте её называют словами.

JSON Schema: обязательные поля списком

«Поле phone обязательное?», «какие значения у статуса?» — по памяти на это не отвечают. Ответ лежит в схеме — описании формы ответа.

{
  "type": "object",
  "required": ["id", "total", "status"],
  "properties": {
    "id": {"type": "integer"},
    "total": {"type": "number", "minimum": 0},
    "status": {"enum": ["NEW", "PAID", "CANCELLED"]}
  }
}

Для проверок это готовый материал. required — негативные проверки: убрать поле и ждать отказа. type — разница между 42 и "42". enumклассы эквивалентности без выдумывания: три значения внутри и любое четвёртое снаружи. minimum — граница со значениями −1, 0 и 1. Схему не ищут отдельно: она внутри описания OpenAPI, и Postman сверяет с ней ответ прямо в проверке.

Чего схема не умеет: {"id": 42, "total": 0, "status": "PAID"} ей соответствует полностью, а по делу это оплаченный заказ на ноль рублей. Форму ловит схема, смысл — тестировщик.

JSONPath: адрес поля

Когда ответ вложен на три уровня, поле надо как-то назвать: в отчёте, в проверке, в разговоре. Для этого есть JSONPath: $.orders[0].customer.name. Здесь $ — корень, точка — шаг вглубь, [0] — элемент массива по номеру, [*] — все сразу: $.orders[*].total соберёт все суммы. Тот же путь понимают Postman и jq, и расплывчатое «имя пустое» становится точным адресом.

XML: атрибут или элемент

Тот же заказ в XML вдвое длиннее, и одно значение записывается двумя способами.

<order id="42">
  <total currency="RUB">4990</total>
</order>

id и currency — атрибуты, total — элемент. Атрибут хранит одно значение и ничего не вкладывает; элемент может повторяться и содержать другие. Что чем быть, решил сам сервис, а путь к значению выходит разный: /order/@id против /order/total.

Префикс вроде soap: или ns2: — пространство имён, объявленное рядом через xmlns, и он часть имени: <ns2:total> и <total> для разборщика разные элементы. Отсюда «глазами поле вижу, а проверка говорит, что его нет».

XML строже JSON: незакрытый тег — не потерянное поле, а нечитаемый ответ целиком. Схема для XML называется XSD.

YAML: где он врёт молча

YAML читают как «просто текст с двоеточиями», а в нём живут настройки стендов, docker-compose и сценарии сборки. Цена ошибки — не упавший разбор, а тихо другое значение.

database:
  host: db.test
  port: 5432
  ssl: no
  version: 1.0

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

ssl: no — не строка «no», а false: разборщики версии 1.1, а их большинство, читают yes, no, on, off как булево. На том же месте спотыкается код страны NO — Норвегия превращается в false. И version: 1.0 — число, равное 1, со строкой "1.0" оно не совпадёт. Лечится одинаково, кавычками: "no", "1.0".

CSV: почему выгрузка ломается

Выгрузку открывают в Excel: вместо таблицы одна колонка, вместо фамилий «РЎРјРёСЂРЅРѕРІ», артикул 4,99E+11. Причин три, и все не про приложение.

Первая — разделитель. Имя формата обещает запятую, её же описывает и RFC 4180, но соблюдать этот документ никто не обязан: Excel в русской локали ждёт точку с запятой, потому что запятая занята под дробную часть. О разделителе договариваются заранее.

Вторая — кавычки. Значение с разделителем, переводом строки или кавычкой внутри берут в двойные кавычки, а саму кавычку удваивают.

id;name
42;"ООО ""Ромашка"", Москва"

Во второй строке два поля: 42 и ООО "Ромашка", Москва — запятая внутри кавычек осталась данными. А выгрузка, собранная склейкой через ; без кавычек, съедет на колонку на первом же адресе с запятой.

Третья — кодировка. UTF-8 без BOM Excel на русской Windows читает как cp1251: те же байты складываются в другие буквы, и «Смирнов» превращается в «РЎРјРёСЂРЅРѕРІ». Помогает BOM в начале файла или импорт через «Данные → Из текста».

Excel и сам правит значения: 00123 становится 123, 1-2 — датой. Поэтому «сломались артикулы» сначала смотрят текстовым редактором: цел файл — испорчен показ, а не данные.

Где какой формат

Формат выбирает не тестировщик, а система на том конце — но по источнику он предсказуем.

ФорматГде встретитсяЧем открыть
JSONответы REST и GraphQL, журналыPostman, DevTools, jq
XMLSOAP, банки и госсервисыSoapUI, Postman
YAMLнастройки стендов, docker-composeредактор с подсветкой
CSVотчёты, прайсы, справочникитекстовый редактор, потом Excel

Коротко

  • В JSON тип виден по записи: "42" — строка, 42 — число; типов для даты и денег нет.
  • Поле со значением null и отсутствующее поле — два разных ответа, и зовут их по-разному.
  • JSON Schema даёт проверки: required — негативные, type — типы, enum — классы эквивалентности, minimum — границы; смысл не ловит.
  • В XML значение бывает атрибутом или элементом, путь разный; префикс — часть имени.
  • YAML ошибается молча: отступ меняет владельца ключа, no становится false, 1.0 — числом; лечат кавычками.
  • CSV ломается на разделителе, кавычках и кодировке — файл сперва смотрят текстовым редактором.

Что почитать дальше