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

Зачем читать и записывать JSON в Node.js
JSON (JavaScript Object Notation) — текстовый формат для структурированных данных. Он удобен для хранения конфигураций, обмена данными между сервисами и сериализации объектов. В Node.js работа с файловой системой реализуется через модуль fs. Умение корректно читать и записывать JSON-файлы позволяет сохранять состояние приложений, кэшировать данные, экспортировать/импортировать наборы данных и быстро прототипировать сервисы.
Важно: JSON — это текст. Перед записью объект нужно превратить в строку (JSON.stringify), а при чтении — распарсить (JSON.parse).
Коротко о модуле fs
Модуль fs встроен в Node.js и предоставляет два основных режима работы:
- Асинхронные методы (не блокируют event loop) — рекомендуются для серверных приложений.
- Синхронные методы (оканчиваются на Sync) — удобны для одноразовых скриптов и запуска в CLI.
Современный стиль — использовать промисы/async-await через путь node:fs/promises (Node.js v18+). Для более старых версий применяется fs/promises.
Чтение JSON-файлов (асинхронно)
Пример чтения файла с использованием промисов и парсинга JSON:
const fs = require("node:fs/promises");
fs.readFile("./users.json", { encoding: "utf-8", flag: "r" })
.then((data) => {
const users = JSON.parse(data);
console.log(users);
})
.catch((error) => {
console.error('Ошибка при чтении JSON-файла:', error);
});Пояснения:
- encoding: “utf-8” — стандарт для текстовых файлов.
- JSON.parse выбрасывает исключение при некорректном JSON — всегда оборачивайте в try/catch или используйте безопасный валидатор.
Альтернатива для маленьких статичных файлов: require(‘./users.json’) — синхронно прочитает и автоматически распарсит JSON, но результат попадёт в кэш require, что не подходит для часто изменяющихся файлов.
Запись JSON-файлов (асинхронно)
Запись требует передачи строки или буфера, поэтому нужно вызвать JSON.stringify на объекте:
const fs = require("node:fs/promises");
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, null, 2), {
encoding: "utf-8",
flag: "w",
}).catch((error) => {
console.error('Ошибка при записи JSON-файла:', error);
});Советы:
- JSON.stringify(obj, null, 2) даёт человекочитаемый отступ, полезно для конфигов.
- Флаг “w” перезаписывает файл; используйте “a” для дозаписи (append) с осторожностью.
Обновление JSON-файлов
fs не имеет отдельного метода «обновить». Общая стратегия — прочитать текущее содержимое, модифицировать структуру в памяти и записать обратно.
Пример функции обновления:
const fs = require("node:fs/promises");
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, null, 2), {
encoding: "utf-8",
flag: "w",
});
return "Файл успешно обновлён";
} catch (error) {
console.error('Ошибка при обновлении JSON-файла:', error);
}
};Вызов функции:
updateFile("./users.json", [
{
id: 4,
name: "Jane Doe",
username: "janedoe123",
address: {
street: "123 Main St",
city: "Anytown",
},
},
{
id: 5,
name: "John Smith",
username: "johnsmith456",
address: {
street: "456 Elm St",
city: "Another City",
},
}
]).then((message) => {
console.log(message);
});Важно: при одновременном доступе нескольких процессов возможны состояния гонки — рассмотрите блокировки, временные файлы и атомарную запись.
Безопасность и валидация
Важно защитить приложение при работе с файлами:
- Всегда валидируйте JSON (структуру и типы) перед использованием данных.
- Не выполняйте код, полученный из JSON (не eval и т.д.).
- Ограничьте права доступа к файлам (chown, chmod, ACL) — файлы конфигурации и данные пользователей должны быть доступны только нужным пользователям/службам.
- Проверяйте пути файлов: не допускайте path traversal (../) через строгую нормализацию и белые списки.
- Логи ошибок не должны содержать чувствительные данные.
Примечание по GDPR/конфиденциальности: если в JSON хранятся персональные данные, убедитесь в наличии контроля доступа, шифрования при хранении и соответствующей политики удаления.
Когда подход с fs.readFile/fs.writeFile не подходит
- Большие файлы: чтение целиком в память может привести к OOM. Для больших объёмов используйте потоковую обработку (streams) или разбиение на чанки.
- Частые параллельные записи: возможны коллизии и потеря данных — применяйте атомарную замену (запись во временный файл + rename) или внешнее хранилище с поддержкой транзакций.
- Высокая нагрузка и многопроцессность: рассмотрите центрированные хранилища (БД, Redis, S3) вместо локальных файлов.
Альтернативные подходы
- Потоковая запись и чтение (fs.createReadStream / fs.createWriteStream) — для больших файлов.
- NDJSON (newline-delimited JSON) — удобен для потоковой обработки и инкрементальных записей.
- Базы данных (Postgres, MongoDB) — для сложных запросов, индексации и контроля параллелизма.
- Объектные хранилища (S3) — для распределённого хранения и резервного копирования.
Паттерны надёжной записи
- Запись во временный файл, проверка завершения записи, затем переименование (atomic rename) заменяет целевой файл.
- Используйте fsync при необходимости гарантировать сброс данных на диск.
- При обновлении — применяйте optimistic locking (версия/etag) или блокировки.
Психологические модели и эвристики при выборе подхода
- Если данные пригодны для представления в виде одного небольшого файла — используйте простую модель read-modify-write.
- Если данные растут по объёму или требуют конкурентного доступа — переходите на потоковую обработку или СУБД.
- Правило «первое — безопасность, второе — производительность»: сначала корректность и целостность данных, затем оптимизация.
Малый справочный блок: важные термины
- JSON: текстовый формат обмена данными.
- fs: модуль файловой системы в Node.js.
- stream: API для обработки данных по частям.
- Atomic rename: операция переименования файла, используемая как атомарная замена.
Чек-листы
Чек-лист для разработчика:
- Использую асинхронные методы fs в серверном коде.
- Валидирую JSON прежде чем использовать.
- Обрабатываю ошибки чтения/записи.
- Не завишу от require для часто меняющихся файлов.
- Для больших файлов применяю потоки.
Чек-лист для DevOps/админа:
- Разрешения файлов ограничены только необходимым процессам.
- Настроены резервные копии и ротация логов/файлов.
- Мониторинг ошибок ввода/вывода и свободного места на диске.
Мини-методология (шаги) для безопасного update
- Прочитать файл в память асинхронно.
- Валидировать и преобразовать данные.
- Подготовить новый контент (сериализация с отступами для читабельности, если нужно).
- Записать в временный файл (tmp).
- Выполнить атомарный rename tmp -> target.
- Логировать успех/ошибки и очищать временные файлы.
Решение: какой метод выбрать (диаграмма)
flowchart TD
A[Нужно работать с JSON?] --> B{Файл небольшой и статичный?}
B -- Да --> C[fs.readFile / fs.writeFile 'с JSON.parse/stringify']
B -- Нет --> D{Требуется потоковая обработка?}
D -- Да --> E[streams 'createReadStream/createWriteStream' или NDJSON]
D -- Нет --> F{Параллельный доступ/транзакции?}
F -- Да --> G[БД/объектное хранилище]
F -- Нет --> CПримеры ошибок и как их избегать
- Ошибка: JSON.parse падает на некорректном JSON. Решение: валидировать вход, использовать try/catch.
- Ошибка: потеря данных при параллельной записи. Решение: атомарная запись и блокировки.
- Ошибка: переполнение памяти при чтении больших файлов. Решение: использовать потоки.
Итоги
Работа с JSON в Node.js простая и гибкая, но требует внимания к безопасности, обработке ошибок и выбору подходящего метода для объёма и характера данных. Для большинства задач используйте асинхронные fs-методы; для больших объёмов — потоки или специализированные хранилища.
Важно: всегда валидируйте данные и ограничивайте доступ к файлам.
Краткое резюме:
- fs.readFile + JSON.parse — для чтения.
- fs.writeFile + JSON.stringify — для записи.
- Для обновления — read-modify-write с атомарной заменой.
- Для больших/конкурентных задач — streams или БД.
1-строчный глоссарий:
- JSON — формат обмена; fs — модуль для работы с файлами; stream — поток данных.
Ключевые шаги при распространённых сценариях:
- Конфиг: слушайте small JSON + человекочитаемый stringify.
- Кэш: require подходит только для неизменяемых, редко обновляемых файлов.
- Логика обновления: используйте временные файлы и atomic rename.
Похожие материалы
3D эффект вырывающегося изображения в Photoshop
Как оценить приложение в App Store на iPhone
Учимся C: Hello World и основы
Где смотреть Супербоул LVIII (2024): кабель и стриминг
Как использовать один номер на двух телефонах