1. Зачем вообще нужен JSON?
JSON (JavaScript Object Notation) — это простой текстовый формат для обмена данными между программами.
В Node.js именно JSON чаще всего используют для хранения и передачи структурированных данных: списков пользователей, настроек, задач, товаров — чего угодно.
Почему именно JSON?
- Его легко читать человеку.
- Его легко парсить и генерировать программой.
- Он почти идеально ложится на объекты JavaScript.
Если вы когда-нибудь работали с API, конфигами или даже package.json — вы уже видели JSON в деле.
Как устроен JSON: кратко и по делу
JSON — это некий “универсальный” способ записать объекты и массивы в виде строки.
Пример объекта в JSON:
{
"name": "Alice",
"age": 30,
"isAdmin": false,
"skills": ["JavaScript", "Node.js"]
}
- Все ключи — строки в двойных кавычках.
- Значения — числа, строки, булевы значения, массивы, объекты, null.
- Никаких комментариев, никаких функций.
В JavaScript такой объект выглядел бы так:
const user = {
name: "Alice",
age: 30,
isAdmin: false,
skills: ["JavaScript", "Node.js"]
};
2. Два главных героя: JSON.parse и JSON.stringify
Работа с JSON в Node.js (и в браузере) всегда строится вокруг двух методов:
- JSON.stringify(obj) — превращает объект в строку JSON.
- JSON.parse(str) — превращает строку JSON обратно в объект.
Пример: сериализация и десериализация
const user = { name: "Bob", age: 25 };
const jsonString = JSON.stringify(user); // '{"name":"Bob","age":25}'
console.log(jsonString);
const parsed = JSON.parse(jsonString); // { name: 'Bob', age: 25 }
console.log(parsed.name); // 'Bob'
Аналогия:
JSON.stringify — это как упаковка посылки для отправки (объект превращается в строку).
JSON.parse — это распаковка посылки (строка снова становится объектом).
3. Чтение JSON-файлов
Теперь перейдём к практике: как прочитать настоящий JSON-файл на диске.
Пример JSON-файла
Пусть у нас есть файл users.json:
[
{ "id": 1, "name": "Alice" },
{ "id": 2, "name": "Bob" }
]
Чтение файла и парсинг
В Node.js читаем файл как строку, а затем превращаем в объект:
const fs = require('fs');
// 1. Читаем файл (синхронно для простоты)
const jsonStr = fs.readFileSync('users.json', 'utf-8');
// 2. Парсим строку в объект
const users = JSON.parse(jsonStr);
console.log(users[0].name); // Alice
Асинхронная версия
const fs = require('fs');
fs.readFile('users.json', 'utf-8', (err, data) => {
if (err) {
console.error('Ошибка чтения файла:', err);
return;
}
try {
const users = JSON.parse(data);
console.log(users[1].name); // Bob
} catch (e) {
console.error('Некорректный JSON:', e.message);
}
});
Обратите внимание:
- Файл читается как строка.
- Только после этого строка превращается в объект через JSON.parse.
- Если в файле ошибка (например, забыли кавычку), JSON.parse выбросит исключение — поэтому используем try/catch.
Типичные ошибки при чтении JSON
Файл не найден: будет ошибка чтения, не парсинга.
Файл не валиден как JSON: будет ошибка при парсинге (SyntaxError: Unexpected token ...).
Файл не в кодировке utf-8: получите странные символы или ошибку.
4. Запись JSON-файлов
Теперь научимся сохранять объекты в виде JSON-файлов.
Преобразуем объект в строку
const user = { name: "Charlie", age: 28 };
const jsonStr = JSON.stringify(user); // '{"name":"Charlie","age":28}'
Записываем строку в файл
const fs = require('fs');
fs.writeFileSync('user.json', jsonStr, 'utf-8');
Асинхронная запись
const fs = require('fs');
fs.writeFile('user.json', jsonStr, 'utf-8', (err) => {
if (err) {
console.error('Ошибка записи файла:', err);
} else {
console.log('Файл успешно сохранён!');
}
});
Форматирование JSON для человека
По умолчанию JSON.stringify делает строку “в одну строку” — неудобно для чтения.
Можно добавить параметры для красивого форматирования:
const jsonStr = JSON.stringify(user, null, 2); // 2 — отступы
fs.writeFileSync('user.json', jsonStr, 'utf-8');
Теперь файл будет выглядеть так:
{
"name": "Charlie",
"age": 28
}
Запись массива объектов
const users = [
{ name: "Alice", age: 22 },
{ name: "Bob", age: 31 }
];
fs.writeFileSync('users.json', JSON.stringify(users, null, 2), 'utf-8');
5. Практика: читаем, добавляем, сохраняем
Давайте попробуем реализовать простую функцию “добавить пользователя в файл”.
Пример кода
const fs = require('fs');
function addUser(newUser) {
// 1. Читаем существующий файл
let users = [];
try {
const data = fs.readFileSync('users.json', 'utf-8');
users = JSON.parse(data);
} catch (e) {
// Если файла нет или он пустой — начинаем с пустого массива
if (e.code !== 'ENOENT') {
console.error('Ошибка чтения/парсинга:', e.message);
return;
}
}
// 2. Добавляем нового пользователя
users.push(newUser);
// 3. Сохраняем обратно
fs.writeFileSync('users.json', JSON.stringify(users, null, 2), 'utf-8');
console.log('Пользователь добавлен!');
}
// Тестируем
addUser({ name: "Dave", age: 40 });
Комментарии к коду
- Если файла нет (ENOENT), начинаем с пустого массива.
- Если файл есть, но повреждён — выводим ошибку.
- После добавления нового пользователя сохраняем массив обратно в файл.
- Отступы (2) делают JSON красивым.
6. Использование с асинхронными методами и Promises
С Node.js 10+ можно использовать fs.promises и работать с async/await:
const fs = require('fs').promises;
async function addUserAsync(newUser) {
let users = [];
try {
const data = await fs.readFile('users.json', 'utf-8');
users = JSON.parse(data);
} catch (e) {
if (e.code !== 'ENOENT') {
console.error('Ошибка:', e.message);
return;
}
}
users.push(newUser);
await fs.writeFile('users.json', JSON.stringify(users, null, 2), 'utf-8');
console.log('Пользователь добавлен (async)!');
}
addUserAsync({ name: "Eve", age: 35 });
7. Типичные ошибки при работе с JSON-файлами
Ошибка №1: забыли преобразовать объект в строку при записи.
Если попытаться записать объект напрямую (fs.writeFileSync('file.json', obj)), получите ошибку: TypeError: Data must be a string or Buffer. Всегда используйте JSON.stringify.
Ошибка №2: забыли парсить строку при чтении.
После чтения файла вы получаете строку, а не объект. Если попытаться обратиться к свойствам строки (data.name), получите undefined.
Ошибка №3: повреждённый JSON-файл.
Если файл был отредактирован вручную и там пропущена запятая или кавычка — JSON.parse выбросит исключение. Всегда используйте try/catch.
Ошибка №4: попытка читать файл, которого нет.
Если файл не существует, readFileSync выбросит ошибку. Обрабатывайте этот случай отдельно — например, начинайте с пустого массива.
Ошибка №5: гонки при асинхронной записи.
Если несколько процессов одновременно пишут в один и тот же JSON-файл, возможна потеря данных. Для сложных сценариев используйте базы данных или блокировки.
Ошибка №6: забыли указать кодировку при чтении/записи.
Если не указать 'utf-8', получите данные в виде Buffer, а не строки. Всегда пишите 'utf-8'.
Ошибка №7: попытка хранить функции или undefined.
JSON не поддерживает функции, undefined, или специальные объекты. Все значения должны быть числами, строками, булевыми, null, массивами или объектами.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ