Перейти к содержимому
Toova
Все инструменты

Форматирование JSON в JavaScript — 5 методов

Toova

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 — при работе с нестандартными структурами данных.