Гид по технологиям

Чтение и запись JSON в Node.js

6 min read Development Обновлено 08 Jan 2026
Чтение и запись JSON в Node.js
Чтение и запись JSON в Node.js

Две металлические секции забора сцеплены цепью; между ними заметен большой зазор, в который вставлен зелёный логотип Node.js.

Зачем работать с JSON в Node.js

JSON — это текстовый формат для обмена структурированными данными. Он лёгкий для чтения человеком и прост для парсинга программами. В Node.js чтение и запись JSON используются для конфигураций, локальных «баз данных» для небольших проектов, экспортов/импортов и промежуточного кэширования.

Кратко: JSON хранит объекты и массивы в виде строки. JSON.parse преобразует строку в объект, JSON.stringify — объект в строку.

Модуль файловой системы Node.js

Node.js содержит встроенный модуль fs для работы с файлами. Он предоставляет синхронные и асинхронные методы. Асинхронные методы не блокируют цикл событий и рекомендуются для серверных приложений. Синхронные методы удобны в простых скриптах и для однопоточных задач при инициализации.

Современный способ — импортировать промис-версию API:

const fs = require("node:fs/promises");  

Если у вас версия Node.js ниже v18, используйте:

const fs = require("fs/promises");  

Чтобы получить и синхронный, и асинхронный функционал, импортируйте без суффикса /promises.

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

Чтение JSON файлов

Метод readFile принимает путь к файлу и опции. Опции могут быть строкой кодировки или объектом с полями encoding и flag.

Часто используемые опции:

  • encoding: строка, по умолчанию “utf8”. Обычно для JSON используйте “utf-8”.
  • flag: строка, например “r” для чтения или “w” для записи.

Пример асинхронного чтения и парсинга:

fs.readFile("./users.json", { encoding: "utf-8", flag: "r" })  
  .then((data) => {  
    const users = JSON.parse(data);  
    console.log(users);  
  })  
  .catch((error) => {  
    console.error('Error reading the JSON file:', error);  
  });  

Советы при чтении:

  • Всегда оборачивайте JSON.parse в try/catch или обрабатывайте ошибку промиса. Неверный JSON вызовет исключение.
  • Для малых конфигурационных файлов можно использовать require(‘./config.json’), но помните про кэширование. require кеширует содержимое модуля — изменения на диске не загрузятся автоматически.
  • Для больших файлов рассмотрите стриминг или разбивку на NDJSON.

Запись JSON файлов

Метод writeFile асинхронно записывает данные. Аргументы: путь, данные (строка или буфер), опции.

Пример базовой записи:

const fakeUsers = [  
  {  
    id: 1,  
    name: "John Doe",  
    username: "johndoe123",  
    address: {  
      street: "123 Main St",  
      city: "Anytown",  
    },  
  },  
  {  
    id: 2,  
    name: "Jane Smith",  
    username: "janesmith456",  
    address: {  
      street: "456 Elm St",  
      city: "Another City",  
    },  
  }  
];  
  
fs.writeFile("./users.json", JSON.stringify(fakeUsers), {  
  encoding: "utf-8",  
  flag: "w",  
}).catch((error) => {  
  console.error('Error writing the JSON file:', error);  
});  

Рекомендации для записи:

  • Преобразуйте объекты в строку через JSON.stringify. Можно передать второй аргумент replacer и третий параметр для отступов, например JSON.stringify(obj, null, 2) для читаемого вывода.
  • Избегайте прямого перезаписывания критичных файлов без атомарной операции.

Атомарная запись и защита от частичного файла

Запись файла напрямую может привести к частично записанному файлу при сбое. Универсальный приём, обеспечивающий атомичность на большинстве платформ:

  1. Записать данные во временный файл в той же директории, например users.json.tmp.
  2. Переименовать временный файл в users.json (fs.rename). Операция переименования обычно атомарна на одном разделе файловой системы.

Пример функции атомарной записи:

const path = require('path');

async function writeAtomically(filePath, data) {
  const dir = path.dirname(filePath);
  const tempPath = path.join(dir, `${path.basename(filePath)}.tmp`);
  await fs.writeFile(tempPath, data, { encoding: 'utf-8', flag: 'w' });
  await fs.rename(tempPath, filePath);
}

// Использование
writeAtomically('./users.json', JSON.stringify(fakeUsers, null, 2)).catch(console.error);

Примечание: атомарное переименование надежно на POSIX и большинстве современных файловых систем. На некоторых сетевых томах или при смешанных файловых системах гарантии могут отличаться.

Обновление JSON файлов

fs не имеет специального метода обновления. Подход прост:

  1. Прочитать файл.
  2. Распарсить в память.
  3. Изменить структуру в памяти.
  4. Записать обратно (лучше атомарно).

Пример функции обновления:

const updateFile = async (filePath, data) => {  
  try {  
    const fileContents = await fs.readFile(filePath, {  
      encoding: "utf-8",  
      flag: "r",  
    });  
  
    const fileData = JSON.parse(fileContents);  
  
    const updatedFileData = [...fileData, ...data];  
  
    await fs.writeFile(filePath, JSON.stringify(updatedFileData), {  
      encoding: "utf-8",  
      flag: "w",  
    });  
  
    return "File updated successfully";  
  } catch (error) {  
    console.error('Error updating the JSON file:', error);  
  }  
};  

Вызов:

updateFile("./users.json", [ /* новые записи */ ]).then((message) => {
  console.log(message);
});

Проблемы конкурентного доступа:

  • Если несколько процессов параллельно читают и записывают один файл, возможна потеря данных. Решения: очередь обновлений, внешняя блокировка (lockfile), использование СУБД или использование atomic rename вместе с уникальными временными файлами и контрольной логикой с повтором при конфликте.

Работа с большими JSON и альтернатива NDJSON

Если файл слишком большой, чтение целиком может привести к нехватке памяти. Варианты:

  • Стриминг JSON не тривиален для массивов, но возможен с парсерами потоков (например, JSONStream или stream-json).
  • Использовать NDJSON (newline-delimited JSON): одна JSON запись на строку. Тогда можно читать и обрабатывать файл построчно.
  • Рассмотреть хранение в базе данных (SQLite, LevelDB, PostgreSQL) для больших или частых обновлений.

Валидация и схемы

Перед использованием данных из JSON проверяйте структуру и типы. Подходы:

  • Лёгкая ручная валидация: проверка обязательных полей и типов.
  • Схемы JSON Schema с валидаторами (ajv и другие).
  • Преобразование и однотипные DTO на входе.

Пример с ajv для проверки структуры пользователей — сократить ошибки на ранней стадии.

Безопасность и защита данных

Основные меры:

  • Валидировать и санитизировать входные данные перед сохранением.
  • Не исполнять код из JSON. Никогда не eval данных из файла.
  • Ограничить доступ к файлам по правам: устанавливайте минимально необходимые права на директории и файлы.
  • Использовать path.normalize и allowlist директорий, чтобы предотвратить directory traversal при работе с путями от пользователя.
  • Шифровать чувствительные поля или весь файл, если он хранит персональные данные.
  • Логи и ошибки не должны раскрывать содержимое файлов с персональными данными.

Примеры проверок пути

const path = require('path');

function safeJoin(base, target) {
  const resolved = path.resolve(base, target);
  if (!resolved.startsWith(path.resolve(base))) {
    throw new Error('Недопустимый путь');
  }
  return resolved;
}

Конкретные сценарии и подходы

Когда использовать что:

  • Конфигурационные файлы на старте приложения: можно читать синхронно при инициализации.
  • Логирование событий и большие записи: лучше использовать ротацию логов и структурированные логи, а не постоянно перезаписывать JSON.
  • Частые конкурентные обновления: используйте базу данных или механизм очередей.
  • Встроенное хранилище для тестов и маленьких утилит: локальные JSON-файлы подходят.

Критерии приёмки

  • Файлы JSON читаются без исключений при корректном формате.
  • При повреждении JSON возвращается понятная ошибка и сохраняется бэкап, если это возможно.
  • Запись выполняется атомарно: либо старое содержимое, либо новое, но не частично записанный файл.
  • Права доступа установлены по принципу минимально необходимого доступа.
  • Автоматические тесты покрывают случаи некорректного JSON, отсутствие файла и параллельные обновления.

План действий при инциденте с файлом

  1. Остановить запись в проблемный файл (при возможности).
  2. Сделать копию текущего файла для расследования.
  3. Восстановить последнюю рабочую версию из бэкапа или версионного контроля.
  4. Прописать проверку целостности и валидности при следующей записи.
  5. Если данные персональные, уведомить ответственных по защите данных согласно регламенту.

Тесты и приёмочные сценарии

  • Позитивный тест: корректный JSON читается и преобразуется в нужную структуру.
  • Негативный тест: повреждённый JSON вызывает читаемую ошибку, приложение не падает непредсказуемо.
  • Тест параллельных записей: два клиента пытаются добавить запись одновременно — итоговые данные не теряются при корректной механике обновления.
  • Тест больших файлов: чтение и обработка не приводят к OOM при использовании стриминга.

Чеклист для разработки и деплоя

  • Используются асинхронные методы fs в рабочем коде.
  • Добавлена проверка JSON.parse и обработка ошибок.
  • Запись реализована атомарно или используется СУБД.
  • Настроена ротация и бэкап критичных файлов.
  • Ограничены права доступа на директории и файлы.
  • Проведены тесты конкурентного доступа.

Локальные альтернативы и миграция

Если проект растёт, рассмотрите миграцию с файлов на подходящую СУБД:

  • Для простоты и встроенности — SQLite.
  • Для множества одновременных пользователей — PostgreSQL или MongoDB.
  • Для простого ключ-значение — LevelDB или Redis.

Миграция обычно включает экспорт JSON → импорт в базу с валидацией схемы.

1‑строчный глоссарий

  • JSON — формат обмена данными в текстовом виде.
  • fs — модуль Node.js для работы с файловой системой.
  • Атомарная запись — техника, гарантирующая «всё или ничего» при сохранении файла.
  • NDJSON — формат, где каждая строка — отдельный JSON-объект.

Заключение

Чтение и запись JSON в Node.js просты, но требуют дисциплины: используйте асинхронные вызовы, валидируйте данные, применяйте атомарную запись и учитывайте особенности больших файлов и конкурентного доступа. Для серьёзных требований по консистентности и масштабу лучше выбрать специализированное хранилище.

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

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

RDP: полный гид по настройке и безопасности
Инфраструктура

RDP: полный гид по настройке и безопасности

Android как клавиатура и трекпад для Windows
Гайды

Android как клавиатура и трекпад для Windows

Советы и приёмы для работы с PDF
Документы

Советы и приёмы для работы с PDF

Calibration в Lightroom Classic: как и когда использовать
Фото

Calibration в Lightroom Classic: как и когда использовать

Отключить Siri Suggestions на iPhone
iOS

Отключить Siri Suggestions на iPhone

Рисование таблиц в Microsoft Word — руководство
Office

Рисование таблиц в Microsoft Word — руководство