JSON в JavaScript

JSON в JavaScript #

JSON (JavaScript Object Notation) — текстовый формат обмена данными: объекты, массивы, строки, числа, true/false и null. В браузере и Node.js пара JSON.stringify / JSON.parse связывает объекты в памяти с телом HTTP-запроса чекаута, ответом списка треков или сохранённой корзиной в localStorage.


Что такое JSON #

JSON — не «весь JavaScript», а узкий поднабор значений:

Допустимо в JSONНет в JSON
объект { "key": value }undefined
массив [1, 2]функции
строка "text"Symbol
число 42, 3.14BigInt (без обходных путей)
true, false, nullDate как тип (только строка)

Ключи объектов в JSON в двойных кавычках:

{
  "orderId": "ord-904",
  "total": 5199,
  "paid": false,
  "lines": [
    { "sku": "kb-01", "qty": 1, "price": 4900 }
  ]
}

JSON.stringify #

Превращает значение JavaScript в строку JSON:

const cart = {
  orderId: 'ord-904',
  lines: [{ sku: 'ms-02', qty: 2 }],
  note: null,
}

const body = JSON.stringify(cart)
// '{"orderId":"ord-904","lines":[{"sku":"ms-02","qty":2}],"note":null}'

Свойства со значением undefined в объекте опускаются; в массиве становятся null:

JSON.stringify({ a: 1, b: undefined }) // '{"a":1}'
JSON.stringify([1, undefined, 3]) // '[1,null,3]'

Форматирование (отступы) #

JSON.stringify(cart, null, 2)

Второй аргумент — replacer (массив ключей или функция), третий — отступ для читаемого лога.

Replacer: скрыть лишнее #

const checkout = {
  orderId: 'ord-1',
  total: 1000,
  cardPan: '4111111111114242',
}

JSON.stringify(checkout, ['orderId', 'total'])
// только перечисленные ключи
JSON.stringify(checkout, (key, value) => {
  if (key === 'cardPan') return undefined
  return value
})

JSON.parse #

Строка → значение JavaScript:

const raw = '{"playlistId":"focus","trackCount":12}'
const data = JSON.parse(raw)

data.playlistId // "focus"
data.trackCount // 12

Битый JSON бросает SyntaxError — оборачивайте в try/catch для пользовательского ввода и внешних API:

function safeParse(text, fallback = null) {
  try {
    return JSON.parse(text)
  } catch {
    return fallback
  }
}

safeParse('{ broken') // null

Типичный цикл API #

async function postCheckout(payload) {
  const response = await fetch('/api/checkout', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`)
  }

  return JSON.parse(await response.text())
}

В современных средах короче:

const data = await response.json() // parse внутри

Date, Map и другие типы #

JSON.stringify(new Date()) даёт строку ISO в кавычках — при parse это снова строка, не Date:

const iso = JSON.stringify({ at: new Date('2026-04-12T10:00:00Z') })
// {"at":"2026-04-12T10:00:00.000Z"}

const parsed = JSON.parse(iso)
typeof parsed.at // "string"

Восстановление даты — вручную:

function reviveDates(key, value) {
  if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
    return new Date(value)
  }
  return value
}

JSON.parse(iso, reviveDates)

Map и Set сериализуются как пустой объект {}, если не преобразовать заранее (см. Map и Set).


Глубокое клонирование через JSON #

const original = { lines: [{ sku: 'a', qty: 1 }], meta: { currency: 'RUB' } }
const clone = JSON.parse(JSON.stringify(original))

clone.lines[0].qty = 99
original.lines[0].qty // 1 — другая ветка

Ограничения: нет функций, undefined, циклических ссылок, BigInt; даты станут строками. Для сложных графов — structuredClone (где доступен).


localStorage и корзина #

localStorage хранит только строки:

const STORAGE_KEY = 'jsflow-cart'

function saveCart(lines) {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(lines))
}

function loadCart() {
  const raw = localStorage.getItem(STORAGE_KEY)
  if (!raw) return []
  return safeParse(raw, [])
}

saveCart([{ sku: 'kb-01', qty: 1 }])
loadCart() // [{ sku: 'kb-01', qty: 1 }]

Версионируйте схему, если поля меняются:

function saveCartV2(lines) {
  localStorage.setItem(
    STORAGE_KEY,
    JSON.stringify({ v: 2, lines })
  )
}

Валидация после parse #

Не доверяйте форме JSON слепо — проверяйте контракт:

function isCartLine(value) {
  return (
    value &&
    typeof value === 'object' &&
    typeof value.sku === 'string' &&
    Number.isInteger(value.qty) &&
    value.qty > 0
  )
}

function normalizeCartPayload(data) {
  if (!data || !Array.isArray(data.lines)) return { lines: [] }
  return {
    lines: data.lines.filter(isCartLine),
  }
}

Для продакшена добавляют схемы (JSON Schema, zod и т.д.) — здесь важен принцип: parse не равен validate.


Практика: экспорт плейлиста #

const playlist = {
  id: 'focus-2026',
  title: 'Deep Work',
  tracks: [
    { id: 't1', title: 'Buffer', durationSec: 240 },
    { id: 't2', title: 'Lane', durationSec: 195 },
  ],
}

const exportBlob = JSON.stringify(
  {
    exportedAt: new Date().toISOString(),
    playlist,
  },
  null,
  2
)

// exportBlob — текст для скачивания .json или отправки в API

Практика: ответ scores API #

const apiText = `
{
  "season": "2026-Q2",
  "entries": [
    { "userId": "u1", "score": 1200 },
    { "userId": "u2", "score": 980 }
  ]
}
`

const { season, entries } = JSON.parse(apiText)

const top = [...entries].sort((a, b) => b.score - a.score)[0]
// { userId: 'u1', score: 1200 }

Итог #

  • JSON — текстовый обмен объектами, массивами и примитивами; ключи в кавычках.
  • JSON.stringify для тела запроса и сохранения; JSON.parse для ответа — с обработкой ошибок.
  • undefined и функции не попадают в JSON как вы ожидаете; даты сериализуются в ISO-строки.
  • После parse проверяйте структуру, особенно для корзины и оплаты.
  • JSON.parse(JSON.stringify(obj)) — быстрый поверхностно-глубокий клон с ограничениями.

См. также #