Полное руководство по форматированию JSON: красиво, минимально, валидно
JSON — универсальный язык API, конфигурационных файлов и обмена данными. Но сырой JSON из ответа API часто представляет собой одну нечитаемую строку, а JSON, написанный вручную, иногда содержит незаметные ошибки, которые всплывают только в runtime. Хороший форматтер делает три вещи: делает JSON читаемым, компактным для передачи и выявляет ошибки до попадания в продакшн. Откройте Форматтер JSON по ходу чтения.
Pretty-print против минификации: когда что использовать
Один и тот же объект JSON можно представить в двух крайних формах:
// Минифицированный — для передачи и хранения
{"user":{"id":42,"name":"Иван Иванов","active":true,"roles":["admin","editor"]}}// Pretty-print — для чтения и редактирования
{
"user": {
"id": 42,
"name": "Иван Иванов",
"active": true,
"roles": [
"admin",
"editor"
]
}
}| Режим | Используйте когда | Избегайте когда |
|---|---|---|
| Pretty-print | Отладка API-ответов, редактирование конфигов, ревью кода, документация, сравнение двух JSON-пейлоадов | Отправка по сети, хранение в БД, встраивание в JS-бандлы |
| Минификация | API-ответы, localStorage, куки, query-параметры, CI-артефакты | Файлы, которые человек читает или редактирует напрямую |
Разница в размере важна в масштабе. API-ответ 50 КБ в pretty-print минифицируется до ~35 КБ — сокращение на 30% без потери информации. При миллионах запросов в день это ощутимо.
Отступы: 2 пробела, 4 пробела или табуляция
Pretty-print требует выбора единицы отступа. Универсального стандарта для JSON нет, но сложились контекстные соглашения:
| Отступ | Распространён в | Компромисс |
|---|---|---|
| 2 пробела | JavaScript-экосистема, package.json, большинство REST API, стандарт Prettier | Компактно; глубокая вложенность укладывается в 80 символов |
| 4 пробела | Python (json.dumps по умолчанию), Java-инструменты | Читабельнее на малой глубине; глубокая вложенность уходит вправо |
| Табуляция | Некоторые Go-инструменты, редакторы с настройкой ширины таба | Визуально адаптируется под редактор; непоследовательно в diff-вьюерах |
В JavaScript JSON.stringify принимает необязательный третий аргумент для отступа:
const obj = { name: "Иван", active: true };
JSON.stringify(obj); // минифицировано: '{"name":"Иван","active":true}'
JSON.stringify(obj, null, 2); // отступ 2 пробела
JSON.stringify(obj, null, 4); // отступ 4 пробела
JSON.stringify(obj, null, ' '); // отступ табуляциейJSON5 и JSONC: комментарии и хвостовые запятые
Стандартный JSON (ECMA-404) не допускает комментариев и хвостовых запятых — это намеренно: JSON является форматом обмена данными, а не языком конфигурации. Но конфигурационные файлы пишутся людьми, а люди хотят комментарии.
Два нестандартных надмножества заполняют этот пробел:
JSONC (JSON с комментариями)
Используется VS Code (settings.json, launch.json), TypeScript (tsconfig.json) и ESLint. Поддерживает комментарии // и /* */, а также хвостовые запятые:
// tsconfig.json — это JSONC
{
"compilerOptions": {
"target": "ES2022", // минимум Node 18
"strict": true,
"moduleResolution": "bundler",
/* включает path aliases */
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"], // хвостовая запятая допустима в JSONC
},
},
}JSON5
Более либеральное надмножество: также разрешает неэкранированные ключи, строки в одинарных кавычках, шестнадцатеричные числа, многострочные строки и +Infinity / -Infinity.
Если вставить JSONC или JSON5 в строгий JSON-валидатор, он отклонит комментарии и хвостовые запятые. Форматтер JSON обрабатывает оба формата — удаляет комментарии и хвостовые запятые перед парсингом. Подробнее в статье Хвостовые запятые и комментарии в JSON.
Валидация: что делает JSON невалидным
JSON имеет строгие правила синтаксиса. Вот самые частые ошибки:
1. Хвостовые запятые
// Невалидный JSON — хвостовая запятая после последнего элемента
{
"name": "Иван",
"active": true, ← ошибка
}2. Одинарные кавычки вместо двойных
// Невалидно — JSON требует двойные кавычки
{ 'name': 'Иван' }
// Валидно
{ "name": "Иван" }3. Ключи без кавычек
// Невалидно — все ключи должны быть строками в кавычках
{ name: "Иван" }
// Валидно
{ "name": "Иван" }4. Комментарии
// Невалидно — в JSON нет синтаксиса комментариев
{
// Это объект пользователя
"name": "Иван"
}5. undefined, NaN, Infinity
// Невалидно — эти JavaScript-значения не представимы в JSON
{
"value": undefined,
"ratio": NaN,
"limit": Infinity
}
// Валидные альтернативы
{
"value": null,
"ratio": null,
"limit": 1.7976931348623157e+308
}6. Неэкранированные управляющие символы в строках
// Невалидно — символ переноса строки внутри строки
{ "text": "строка первая
строка вторая" }
// Валидно — используйте escape-последовательности
{ "text": "строка первая
строка вторая" }7. Числа с ведущими нулями
// Невалидно — JSON запрещает ведущие нули (восьмеричная неоднозначность)
{ "code": 007 }
// Валидно
{ "code": 7 }Сортировка ключей
Объекты JSON технически неупорядочены — спецификация не гарантирует порядок ключей. На практике большинство парсеров сохраняют порядок вставки, но полагаться на это не стоит.
Сортировка ключей полезна для:
- Сравнения (diff): Два JSON-объекта с одинаковыми данными, но разным порядком ключей, дают «шумный» diff. Отсортируйте оба перед сравнением.
- Кэширования: Если вы хэшируете JSON как ключ кэша, несортированные ключи могут давать разные хэши для идентичных данных.
- Консистентности в git: Отсортированные ключи означают, что git diff показывает только изменения данных, а не перестановки.
// Рекурсивная сортировка ключей
function sortKeys(obj) {
if (Array.isArray(obj)) return obj.map(sortKeys);
if (obj !== null && typeof obj === 'object') {
return Object.fromEntries(
Object.entries(obj)
.sort(([a], [b]) => a.localeCompare(b))
.map(([k, v]) => [k, sortKeys(v)])
);
}
return obj;
}
const sorted = JSON.stringify(sortKeys(data), null, 2);В командной строке jq сортирует ключи флагом --sort-keys:
jq --sort-keys . input.json > sorted.jsonРабота с большими JSON-файлами
Вставить файл 10 МБ в браузерный форматтер — можно, но парсинг и рендеринг большого дерева может быть медленным. Для больших файлов быстрее использовать консольные инструменты:
# Pretty-print
jq . large.json
# Минификация
jq -c . large.json
# Извлечь вложенное значение
jq '.users[0].profile.name' large.json
# Фильтрация массива
jq '[.users[] | select(.active == true)]' large.jsonФорматирование JSON в коде
JavaScript / TypeScript
// Pretty-print
const pretty = JSON.stringify(data, null, 2);
// Минификация
const minified = JSON.stringify(data);
// С replacer — исключить null-значения
const clean = JSON.stringify(data, (key, value) =>
value === null ? undefined : value
, 2);
// Безопасный парсинг
function safeParseJSON(str) {
try {
return { ok: true, data: JSON.parse(str) };
} catch (e) {
return { ok: false, error: e.message };
}
}Python
import json
# Pretty-print
print(json.dumps(data, indent=2, ensure_ascii=False))
# Минификация (без пробелов)
print(json.dumps(data, separators=(',', ':')))
# Сортировка ключей
print(json.dumps(data, indent=2, sort_keys=True))
# Парсинг с обработкой ошибок
try:
parsed = json.loads(raw_string)
except json.JSONDecodeError as e:
print(f"Ошибка JSON в строке {e.lineno}, столбце {e.colno}: {e.msg}")Двойное кодирование JSON
Частая ловушка: JSON из API иногда двойно закодирован — объект JSON сериализован в строку, и эта строка является значением в другом JSON:
// Двойное кодирование: значение — строка, содержащая JSON
{
"payload": "{"user":{"id":42,"name":"Иван"}}"
}Чтобы работать с внутренним JSON, нужно парсить дважды — или использовать Разэкранирование строк JSON для извлечения внутреннего пейлоада.
Краткая справка
| Задача | Инструмент |
|---|---|
| Pretty-print или минификация JSON | Форматтер JSON |
| Валидация и просмотр точной ошибки | Форматтер JSON |
| Разэкранирование двойно закодированной строки | Разэкранирование JSON |
| Запросы к вложенным значениям через dot-path | JSONPath Tester |
| Конвертация JSON в CSV для таблиц | JSON ↔ CSV Конвертер |
| Валидация JSON по схеме | Валидатор JSON Schema |
Форматируйте, валидируйте и изучайте JSON прямо в браузере с помощью Форматтера JSON — данные не покидают ваш компьютер.