Форматирование JSON в JavaScript — 5 методов
JSON повсюду: ответы API, конфигурационные файлы, экспорты баз данных, конвейеры логов. Но «сырой» вывод JSON зачастую представляет собой компактный блок — без переносов строк, без отступов, ключи в том порядке, в котором их выводит сериализатор. Это затрудняет отладку и усложняет совместную работу.
JavaScript предоставляет несколько способов форматирования JSON: от встроенного JSON.stringify до потоковых библиотек для файлов, слишком больших для загрузки в память. В этом руководстве разбираются пять практических методов — наиболее распространённые случаи и граничные ситуации, которые застают разработчиков врасплох.
Метод 1: JSON.stringify с отступом
Простейший подход уже встроен в язык. JSON.stringify принимает три аргумента: сериализуемое значение, заменитель (об этом — в методе 2) и параметр space, управляющий отступом.
const data = {
name: "Alice",
role: "engineer",
skills: ["JavaScript", "TypeScript", "Node.js"],
active: true,
};
// Отступ 2 пробела (распространённый стандарт)
console.log(JSON.stringify(data, null, 2));
// Отступ 4 пробела
console.log(JSON.stringify(data, null, 4));
// Отступ табуляцией
console.log(JSON.stringify(data, null, "\t"));
С параметром null, 2 вывод выглядит так:
{
"name": "Alice",
"role": "engineer",
"skills": [
"JavaScript",
"TypeScript",
"Node.js"
],
"active": true
}
Два пробела — наиболее распространённое соглашение в JavaScript-проектах: это стандарт по умолчанию в ESLint, Prettier и большинстве руководств по стилю. Четыре пробела распространены в инструментах, ориентированных на Python. Когда space опущен или равен 0, вывод представляет собой компактную однострочную строку — оптимально для передачи по сети.
Также можно мгновенно форматировать JSON в браузере с помощью инструмента JSON Formatter — вставьте «сырой» JSON, получите красиво отформатированный вывод и скопируйте результат без написания какого-либо кода.
Метод 2: пользовательская функция-заменитель
Второй аргумент JSON.stringify — заменитель — это функция или массив, фильтрующий и преобразующий значения перед сериализацией. Именно здесь вы получаете настоящий контроль над тем, что попадает в вывод.
Форма с массивом — простейший подход для включения только нужных ключей:
const user = {
id: 42,
name: "Bob",
password: "s3cr3t",
email: "bob@example.com",
createdAt: new Date(),
};
// Заменитель-массив: включить только перечисленные ключи
const safeJson = JSON.stringify(user, ["id", "name", "email"], 2);
console.log(safeJson);
// {
// "id": 42,
// "name": "Bob",
// "email": "bob@example.com"
// }
Форма с функцией даёт контроль над каждым значением. Верните значение, чтобы включить его; верните undefined, чтобы исключить; или верните преобразованное значение:
function replacer(key, value) {
// Убираем ключи, начинающиеся с подчёркивания (приватное соглашение)
if (key.startsWith("_")) return undefined;
// Маскируем чувствительные поля
if (key === "password" || key === "token") return "[REDACTED]";
// Явно преобразуем объекты Date в ISO-строки
if (value instanceof Date) return value.toISOString();
return value;
}
const payload = {
id: 1,
name: "Carol",
password: "hunter2",
_internalFlag: true,
lastLogin: new Date("2026-05-01"),
};
console.log(JSON.stringify(payload, replacer, 2));
// {
// "id": 1,
// "name": "Carol",
// "password": "[REDACTED]",
// "lastLogin": "2026-05-01T00:00:00.000Z"
// } Заменитель применяется рекурсивно к каждому вложенному объекту и массиву. Этот паттерн полезен для конвейеров логирования, где нужно убрать учётные данные перед записью на диск или отправкой в систему наблюдаемости.
Метод 3: красивый вывод с сортировкой ключей
JavaScript-объекты не гарантируют порядок ключей (хотя V8 и большинство движков сохраняют порядок вставки для строковых ключей). Когда нужен детерминированный вывод — для сравнения различий (diff), кеширования или канонических представлений — сортировка ключей по алфавиту — верное решение.
function sortedStringify(value, indent = 2) {
return JSON.stringify(value, sortReplacer, indent);
}
function sortReplacer(key, value) {
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
return Object.keys(value)
.sort()
.reduce((sorted, k) => {
sorted[k] = value[k];
return sorted;
}, {});
}
return value;
}
const config = {
version: "1.0",
author: "Dave",
dependencies: { typescript: "^5.4", eslint: "^9.0", astro: "^5.0" },
name: "my-project",
};
console.log(sortedStringify(config));
// {
// "author": "Dave",
// "dependencies": { "astro": "^5.0", "eslint": "^9.0", "typescript": "^5.4" },
// "name": "my-project",
// "version": "1.0"
// }
Сортировка ключей означает, что git-diff показывает только фактически изменённые строки, а не произвольные перестановки от разных сериализаторов. Особенно это полезно для package.json и аналогичных конфигурационных файлов, хранящихся в системе контроля версий.
Для конвертации JSON в другие форматы инструменты JSON to YAML и JSON to CSV также учитывают порядок ключей в своём выводе.
Метод 4: форматирование из строки (parse + stringify)
В реальных условиях JSON обычно приходит в виде строки — из ответа fetch, прочитанного файла, буфера обмена или текстового столбца базы данных. Нужно сначала разобрать (parse) его, затем переформатировать. Ключевой момент — корректная обработка ошибок: некорректный JSON выбросит исключение, и его нужно перехватить.
function formatJsonString(rawString, indent = 2) {
try {
const parsed = JSON.parse(rawString);
return { ok: true, result: JSON.stringify(parsed, null, indent) };
} catch (err) {
return { ok: false, error: err.message };
}
}
const raw = '{"name":"Eve","scores":[100,95,88],"active":true}';
const { ok, result, error } = formatJsonString(raw);
if (ok) {
console.log(result);
// {
// "name": "Eve",
// "scores": [100, 95, 88],
// "active": true
// }
} else {
console.error("Ошибка разбора:", error);
}
// Некорректный ввод
const bad = '{"name": "Eve", "broken":}';
const r2 = formatJsonString(bad);
// { ok: false, error: "Unexpected token '}'" } Обёртывание ошибок разбора в структурированный возвращаемый объект делает эту функцию безопасной для использования в UI-компонентах и сборочных скриптах без необходимости оборачивать каждый вызов в try/catch. Документация MDN по JSON.stringify охватывает полную спецификацию параметров, а RFC 8259 определяет, как выглядит корректный JSON на уровне протокола.
Метод 5: потоковая обработка для больших файлов
Методы 1–4 загружают всю структуру JSON в память перед форматированием. Для файлов размером в сотни мегабайт или несколько гигабайт это блокирует цикл событий Node.js и может привести к аварийному завершению процесса.
Потоковый подход считывает файл по частям и записывает отформатированный вывод постепенно. Для NDJSON (один JSON-объект на строку, распространённый в файлах логов и экспортах баз данных) подход на основе readline работает без дополнительных зависимостей:
import { createReadStream, createWriteStream } from "node:fs";
import { createInterface } from "node:readline";
async function formatNdjsonFile(inputPath, outputPath) {
const rl = createInterface({
input: createReadStream(inputPath),
crlfDelay: Infinity,
});
const output = createWriteStream(outputPath);
for await (const line of rl) {
if (!line.trim()) continue;
try {
const obj = JSON.parse(line);
output.write(JSON.stringify(obj, null, 2) + "\n---\n");
} catch (e) {
output.write("[Некорректная строка JSON: " + e.message + "]\n---\n");
}
}
output.end();
} NDJSON — простейший потоковый формат: каждая строка является полным корректным JSON-объектом. Многие инструменты экспорта поддерживают его именно потому, что он тривиально поддаётся потоковой обработке. Если вы управляете форматом больших экспортов данных, предпочтите NDJSON одному большому JSON-массиву.
Граничные случаи, о которых нужно знать
Это сценарии, в которых стандартное форматирование JSON либо молча даёт неверный результат, либо выбрасывает исключение неожиданно.
Циклические ссылки
JSON.stringify выбрасывает TypeError, если объект ссылается на себя напрямую или косвенно. Исправьте это с помощью заменителя, отслеживающего посещённые объекты через WeakSet:
function safeStringify(obj, indent = 2) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) return "[Circular]";
seen.add(value);
}
return value;
}, indent);
}
const a = { name: "circular" };
a.self = a;
console.log(safeStringify(a));
// { "name": "circular", "self": "[Circular]" } WeakSet хранит ссылки, не препятствуя сборке мусора, что позволяет избежать утечек памяти в длительно работающих процессах.
Значения BigInt
JSON.stringify выбрасывает TypeError для значений BigInt, поскольку в спецификации JSON нет 64-битного целочисленного типа. Преобразуйте их в строку в заменителе:
const data = { id: 9007199254740993n, value: 42 };
JSON.stringify(data, (key, value) =>
typeof value === "bigint" ? value.toString() : value
, 2);
// { "id": "9007199254740993", "value": 42 } Значения Map и Set
Map сериализуется как пустой объект, а Set — как пустой массив, а не их содержимое. Явно преобразуйте их в заменителе:
const data = {
tags: new Set(["json", "javascript"]),
meta: new Map([["source", "api"]]),
};
JSON.stringify(data, (key, value) => {
if (value instanceof Set) return [...value];
if (value instanceof Map) return Object.fromEntries(value);
return value;
}, 2);
// { "tags": ["json", "javascript"], "meta": { "source": "api" } } Значения undefined
Свойства объектов со значением undefined молча удаляются. Элементы массива со значением undefined становятся null. Используйте заменитель для преобразования undefined в null, если нужно сохранить все ключи:
const obj = { a: 1, b: undefined, c: null };
JSON.stringify(obj, null, 2);
// { "a": 1, "c": null } — "b" молча удаляется
// Исправление:
JSON.stringify(obj, (key, value) =>
value === undefined ? null : value
, 2);
// { "a": 1, "b": null, "c": null } Мгновенное форматирование JSON в браузере
Если нужно отформатировать блок JSON прямо сейчас без написания кода, JSON Formatter от Toova справится одним нажатием — вставьте «сырой» JSON, получите красиво отформатированный вывод с отступом в 2 или 4 пробела и скопируйте результат. Без регистрации, без загрузки файлов, всё работает локально в браузере.
Для конвертации между форматами JSON to YAML и JSON to CSV придерживаются того же принципа приоритета конфиденциальности — ваши данные никогда не покидают устройство.
Заключение
Для большинства случаев JSON.stringify(obj, null, 2) — всё, что нужно. Добавляйте функцию-заменитель, когда нужна фильтрация, маскирование или сортировка ключей. Оборачивайте JSON.parse в try/catch при работе с внешними данными. Прибегайте к потоковой обработке только тогда, когда размер файла делает синхронный разбор непрактичным. И держите в уме граничные случаи — циклические ссылки, BigInt, Map/Set, undefined — при работе с нестандартными структурами данных.