Hack Frontend Community

JSON.parse и JSON.stringify в JavaScript

Что такое JSON?

JSON (JavaScript Object Notation) — текстовый формат обмена данными, основанный на синтаксисе JavaScript.

JavaScript предоставляет два основных метода для работы с JSON:

  • JSON.stringify() — преобразование объекта в JSON-строку (сериализация)
  • JSON.parse() — преобразование JSON-строки в объект (десериализация)

JSON.stringify()

Преобразует JavaScript-значение в JSON-строку.

Базовый синтаксис

JSON.stringify(value, replacer, space)

Параметры

  1. value — значение для преобразования
  2. replacer (опционально) — функция или массив для фильтрации свойств
  3. space (опционально) — количество пробелов для форматирования

Простые примеры

const user = {
  name: 'Иван',
  age: 25,
  isActive: true
};

JSON.stringify(user);
// '{"name":"Иван","age":25,"isActive":true}'

// С форматированием
JSON.stringify(user, null, 2);
/*
{
  "name": "Иван",
  "age": 25,
  "isActive": true
}
*/

Что можно сериализовать?

Поддерживаемые типы

// Объекты
JSON.stringify({ a: 1 });  // '{"a":1}'

// Массивы
JSON.stringify([1, 2, 3]);  // '[1,2,3]'

// Строки
JSON.stringify('text');  // '"text"'

// Числа
JSON.stringify(42);  // '42'

// Boolean
JSON.stringify(true);  // 'true'

// null
JSON.stringify(null);  // 'null'

Что игнорируется или преобразуется

const obj = {
  fn: function() {},           // Функция - игнорируется
  undef: undefined,            // undefined - игнорируется
  sym: Symbol('id'),           // Symbol - игнорируется
  date: new Date(),            // Дата → строка
  nan: NaN,                    // NaN → null
  infinity: Infinity,          // Infinity → null
  regex: /test/,               // RegExp → {}
  map: new Map([[1, 'one']]),  // Map → {}
  set: new Set([1, 2, 3])      // Set → {}
};

JSON.stringify(obj);
// '{"date":"2024-01-01T00:00:00.000Z","nan":null,"infinity":null,"regex":{},"map":{},"set":{}}'

Важно:

undefined, функции и символы:

  • В объектах — игнорируются
  • В массивах — становятся null
JSON.stringify([1, undefined, function() {}, 3]);
// '[1,null,null,3]'

Параметр replacer

Массив (фильтрация свойств)

const user = {
  name: 'Иван',
  age: 25,
  password: 'secret123',
  email: 'ivan@example.com'
};

// Сериализовать только name и email
JSON.stringify(user, ['name', 'email']);
// '{"name":"Иван","email":"ivan@example.com"}'

Функция (преобразование значений)

const obj = {
  name: 'Иван',
  age: 25,
  salary: 100000
};

JSON.stringify(obj, (key, value) => {
  // Скрываем зарплату
  if (key === 'salary') return undefined;
  
  // Удваиваем возраст
  if (key === 'age') return value * 2;
  
  return value;
});
// '{"name":"Иван","age":50}'

Параметры функции replacer

  • key — ключ свойства (пустая строка для корневого объекта)
  • value — значение свойства
  • this — родительский объект
const data = { a: 1, nested: { b: 2 } };

JSON.stringify(data, function(key, value) {
  console.log(`key: "${key}", value:`, value);
  return value;
});

// key: "", value: { a: 1, nested: { b: 2 } }  ← корневой объект
// key: "a", value: 1
// key: "nested", value: { b: 2 }
// key: "b", value: 2

Параметр space (форматирование)

const obj = { name: 'Иван', age: 25 };

// Число - количество пробелов
JSON.stringify(obj, null, 2);
/*
{
  "name": "Иван",
  "age": 25
}
*/

// Строка - префикс каждого уровня
JSON.stringify(obj, null, '→ ');
/*
{
→ "name": "Иван",
→ "age": 25
}
*/

Метод toJSON()

Объекты могут определить собственное поведение сериализации через метод toJSON().

const user = {
  name: 'Иван',
  birthDate: new Date(1998, 5, 15),
  
  toJSON() {
    return {
      name: this.name,
      age: new Date().getFullYear() - this.birthDate.getFullYear()
    };
  }
};

JSON.stringify(user);
// '{"name":"Иван","age":26}'

Встроенный toJSON у Date

const date = new Date();
date.toJSON();  // "2024-01-15T12:00:00.000Z"

JSON.stringify({ created: date });
// '{"created":"2024-01-15T12:00:00.000Z"}'

Циклические ссылки

const obj = { name: 'Иван' };
obj.self = obj;  // Циклическая ссылка

try {
  JSON.stringify(obj);
} catch (error) {
  console.error(error);
  // TypeError: Converting circular structure to JSON
}

Решение: Использовать WeakSet

function stringifyWithoutCircular(obj) {
  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;
  });
}

const obj = { name: 'Иван' };
obj.self = obj;

stringifyWithoutCircular(obj);
// '{"name":"Иван","self":"[Circular]"}'

JSON.parse()

Преобразует JSON-строку в JavaScript-значение.

Базовый синтаксис

JSON.parse(text, reviver)

Параметры

  1. text — JSON-строка для парсинга
  2. reviver (опционально) — функция преобразования значений

Примеры

JSON.parse('{"name":"Иван","age":25}');
// { name: 'Иван', age: 25 }

JSON.parse('[1, 2, 3]');
// [1, 2, 3]

JSON.parse('true');   // true
JSON.parse('null');   // null
JSON.parse('"text"'); // "text"
JSON.parse('42');     // 42

Параметр reviver

Функция для преобразования значений при парсинге.

const json = '{"name":"Иван","birthDate":"1998-06-15T00:00:00.000Z"}';

const user = JSON.parse(json, (key, value) => {
  if (key === 'birthDate') {
    return new Date(value);  // Преобразуем строку в Date
  }
  return value;
});

console.log(user.birthDate instanceof Date);  // true

Параметры функции reviver

  • key — ключ свойства
  • value — значение свойства
  • this — родительский объект

Функция вызывается снизу вверх (от вложенных к корню).

const json = '{"a":1,"nested":{"b":2}}';

JSON.parse(json, (key, value) => {
  console.log(`key: "${key}", value:`, value);
  return value;
});

// key: "a", value: 1
// key: "b", value: 2
// key: "nested", value: { b: 2 }
// key: "", value: { a: 1, nested: { b: 2 } }  ← последний

Ошибки парсинга

// Синтаксическая ошибка
try {
  JSON.parse("{name: 'Иван'}");  // Ключи должны быть в кавычках
} catch (error) {
  console.error(error);
  // SyntaxError: Unexpected token n in JSON at position 1
}

// Незакрытая строка
JSON.parse('{"name": "Иван}');  // SyntaxError

// Trailing comma
JSON.parse('{"a": 1,}');  // SyntaxError

// Правильный JSON
JSON.parse('{"name":"Иван"}');  // OK

JSON строже JavaScript:

  • Ключи обязательно в двойных кавычках
  • Нельзя использовать trailing commas
  • Только двойные кавычки для строк
  • Нет комментариев

Deep Clone с помощью JSON

Простой способ клонирования

const original = {
  name: 'Иван',
  address: {
    city: 'Москва'
  }
};

const clone = JSON.parse(JSON.stringify(original));

clone.address.city = 'Питер';

console.log(original.address.city);  // 'Москва'
console.log(clone.address.city);     // 'Питер'

Ограничения метода

const obj = {
  date: new Date(),
  fn: () => {},
  undef: undefined,
  map: new Map([[1, 'one']]),
  set: new Set([1, 2])
};

const clone = JSON.parse(JSON.stringify(obj));

console.log(clone);
// { date: '2024-01-15T12:00:00.000Z' }  ← Date стал строкой
// fn, undef, map, set — потеряны

Правильный Deep Clone

Для полноценного клонирования используйте structuredClone() (современный стандарт):

const original = {
  date: new Date(),
  map: new Map([[1, 'one']]),
  set: new Set([1, 2])
};

const clone = structuredClone(original);

console.log(clone.date instanceof Date);  // true 
console.log(clone.map instanceof Map);    // true 

Практические примеры

LocalStorage

// Сохранение
const user = { name: 'Иван', settings: { theme: 'dark' } };
localStorage.setItem('user', JSON.stringify(user));

// Загрузка
const loadedUser = JSON.parse(localStorage.getItem('user'));

API запросы

fetch('/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ name: 'Иван', age: 25 })
})
  .then(response => response.json())
  .then(data => console.log(data));

Сравнение объектов

function deepEqual(obj1, obj2) {
  return JSON.stringify(obj1) === JSON.stringify(obj2);
}

deepEqual({ a: 1, b: 2 }, { a: 1, b: 2 });  // true
deepEqual({ a: 1, b: 2 }, { b: 2, a: 1 });  // false (порядок ключей важен)

Осторожно:

Этот метод работает только для простых объектов и зависит от порядка ключей.


Производительность

const bigObject = { /* 10000 свойств */ };

console.time('stringify');
const json = JSON.stringify(bigObject);
console.timeEnd('stringify');  // ~10ms

console.time('parse');
JSON.parse(json);
console.timeEnd('parse');  // ~5ms
  • JSON.parse() обычно быстрее JSON.stringify()
  • Для больших объектов может быть медленно
  • Рассмотрите streaming парсеры для огромных JSON

Вывод

JSON.stringify():

  • Сериализует объекты, массивы, примитивы
  • Игнорирует функции, undefined, Symbol
  • Поддерживает replacer для фильтрации и преобразования
  • Поддерживает space для форматирования
  • Можно кастомизировать через toJSON()
  • Не работает с циклическими ссылками

JSON.parse():

  • Парсит валидный JSON
  • Поддерживает reviver для преобразования
  • Строже JavaScript (кавычки, trailing commas)
  • Может выбросить SyntaxError

Deep Clone через JSON:

  • Простой способ
  • Теряет функции, Date, Map, Set
  • Используйте structuredClone() для полноценного клонирования

На собеседовании:

Частые вопросы:

  • Что теряется при JSON.stringify()?
  • Как обработать циклические ссылки?
  • Чем отличается JSON.parse() от eval()?
  • Как сделать deep clone?
  • Для чего нужны replacer и reviver?