← JavaScript/JSON#74 из 383← ПредыдущийСледующий →+20 XP
Полезно по теме:Гайд: как учить JavaScriptПрактика: JS базаПрактика: async и сетьТермин: Closure

JSON

Реальная проблема: как передать данные между сервером и браузером

Когда вы открываете ленту Instagram, браузер делает запрос к серверу. Сервер написан на Python или Go — совсем другой язык, другие типы данных. Как передать объект? Его нужно сериализовать в текст — и JSON стал универсальным языком для этого. Сегодня JSON используется в 99% веб-API.

Что такое JSON

JSON (JavaScript Object Notation) — текстовый формат данных. Выглядит как JS-объект, но со строгими правилами:

  • Ключи только в двойных кавычках
  • Строки — только двойные кавычки
  • Значения: строки, числа, булевы, null, массивы, объекты
  • Нет функций, нет undefined, нет комментариев, нет trailing comma
  • На основе предыдущих уроков

  • «Объекты» — JSON похож на объектный литерал, но строже
  • «Массивы» — массивы в JSON: [1, 2, 3]
  • «Копирование объектов» — JSON как способ глубокого копирования
  • «try/catch» (впереди) — JSON.parse может выбросить ошибку
  • JSON.stringify — объект в строку (сериализация)

    const user = {
      name: 'Алексей',
      age: 28,
      isAdmin: false,
      tags: ['js', 'react'],
    }
    
    JSON.stringify(user)
    // '{"name":"Алексей","age":28,"isAdmin":false,"tags":["js","react"]}'
    
    // С форматированием (для читаемости / логов):
    JSON.stringify(user, null, 2)
    // {
    //   "name": "Алексей",
    //   "age": 28,
    //   ...
    // }

    JSON.parse — строка в объект (десериализация)

    const str = '{"name":"Алексей","age":28}'
    const obj = JSON.parse(str)
    console.log(obj.name)  // 'Алексей'
    console.log(typeof obj.age)  // 'number' — тип восстановлен

    Что теряется при stringify

    const data = {
      name: 'Тест',
      fn: function() {},    // функция — ИГНОРИРУЕТСЯ
      symbol: Symbol(),     // Symbol — ИГНОРИРУЕТСЯ
      undef: undefined,     // undefined — ИГНОРИРУЕТСЯ
      date: new Date(),     // Date → строка '2024-01-15T10:30:00.000Z'
      map: new Map(),       // Map → {}
      regexp: /abc/,        // RegExp → {}
    }
    
    console.log(JSON.stringify(data))
    // {"name":"Тест","date":"2024-01-15T10:30:00.000Z","map":{},"regexp":{}}

    replacer и reviver

    // replacer — управляет что включать в JSON
    const sensitive = { name: 'Иван', password: 'secret', age: 25 }
    const safe = JSON.stringify(sensitive, ['name', 'age'])  // только эти поля
    // '{"name":"Иван","age":25}'
    
    // reviver — восстанавливает типы при парсинге
    const withDate = '{"name":"Иван","createdAt":"2024-01-15T10:00:00.000Z"}'
    const parsed = JSON.parse(withDate, (key, value) => {
      if (key === 'createdAt') return new Date(value)  // строку → Date
      return value
    })
    console.log(parsed.createdAt instanceof Date)  // true

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

    // Простой способ — работает для простых данных без функций и Date
    const original = { user: { name: 'Алексей', scores: [10, 20, 30] } }
    const copy = JSON.parse(JSON.stringify(original))
    copy.user.name = 'Иван'
    console.log(original.user.name)  // 'Алексей' — не изменился
    
    // Лучший способ (ES2022):
    const copy = structuredClone(original)  // сохраняет Date, Map, Set

    Типичные ошибки

    Ошибка 1: JSON.parse без try/catch

    // Сломано — если строка невалидна, выбросит SyntaxError:
    const data = JSON.parse(localStorage.getItem('data'))  // может упасть!
    
    // Исправлено:
    function safeParse(str, fallback = null) {
      try {
        return JSON.parse(str)
      } catch {
        return fallback
      }
    }

    Ошибка 2: циклические ссылки

    // Сломано:
    const obj = { name: 'test' }
    obj.self = obj  // obj ссылается на себя
    JSON.stringify(obj)  // TypeError: Converting circular structure to JSON
    
    // Исправлено — structuredClone тоже не поможет, нужна обработка:
    // Используй библиотеку или вручную удали циклическую ссылку

    Ошибка 3: путаница с Date

    // Date → строка при stringify, обратно не восстанавливается автоматически:
    const obj = { date: new Date() }
    const str = JSON.stringify(obj)
    const back = JSON.parse(str)
    console.log(back.date instanceof Date)  // false — это строка!
    console.log(typeof back.date)           // 'string'
    
    // Исправлено — используй reviver или конвертируй явно:
    new Date(back.date)

    В реальных проектах

  • REST API: запросы и ответы в формате JSON (Content-Type: application/json)
  • localStorage: хранение объектов через stringify/parse
  • Конфигурационные файлы: package.json, tsconfig.json, .eslintrc.json
  • Логирование: console.log(JSON.stringify(data, null, 2)) для читаемых логов
  • WebSocket: передача сообщений как JSON-строк
  • Примеры

    Работа с API: сериализация, десериализация, безопасный парсинг

    // Симуляция работы с API ответом
    const rawApiResponse = `{
      "status": "ok",
      "data": {
        "products": [
          {"id": 1, "name": "Ноутбук", "price": 75000, "inStock": true},
          {"id": 2, "name": "Мышь",    "price": 1500,  "inStock": false},
          {"id": 3, "name": "Коврик",  "price": 500,   "inStock": true}
        ],
        "total": 3,
        "updatedAt": "2024-01-15T10:30:00.000Z"
      }
    }`
    
    // Парсинг с восстановлением дат
    function parseApiResponse(jsonStr) {
      try {
        return JSON.parse(jsonStr, (key, value) => {
          if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
            return new Date(value)
          }
          return value
        })
      } catch (e) {
        console.error('Ошибка парсинга:', e.message)
        return null
      }
    }
    
    const response = parseApiResponse(rawApiResponse)
    console.log(response.status)                        // 'ok'
    console.log(response.data.total)                    // 3
    console.log(response.data.updatedAt instanceof Date) // true
    
    // Фильтрация и сериализация для сохранения
    const inStock = response.data.products.filter(p => p.inStock)
    const forSave = JSON.stringify({ products: inStock }, null, 2)
    console.log(forSave)
    // {
    //   "products": [
    //     { "id": 1, "name": "Ноутбук", "price": 75000, "inStock": true },
    //     { "id": 3, "name": "Коврик",  "price": 500,   "inStock": true }
    //   ]
    // }
    
    // Безопасное хранение в "localStorage" (симуляция)
    const storage = {}
    function saveToStorage(key, data) {
      storage[key] = JSON.stringify(data)
    }
    function loadFromStorage(key, fallback = null) {
      try {
        const raw = storage[key]
        return raw != null ? JSON.parse(raw) : fallback
      } catch {
        return fallback
      }
    }
    
    saveToStorage('cart', inStock)
    const loaded = loadFromStorage('cart', [])
    console.log(loaded.length)         // 2
    console.log(loaded[0].name)        // 'Ноутбук'
    console.log(loadFromStorage('x', []).length)  // 0 — fallback

    JSON

    Реальная проблема: как передать данные между сервером и браузером

    Когда вы открываете ленту Instagram, браузер делает запрос к серверу. Сервер написан на Python или Go — совсем другой язык, другие типы данных. Как передать объект? Его нужно сериализовать в текст — и JSON стал универсальным языком для этого. Сегодня JSON используется в 99% веб-API.

    Что такое JSON

    JSON (JavaScript Object Notation) — текстовый формат данных. Выглядит как JS-объект, но со строгими правилами:

  • Ключи только в двойных кавычках
  • Строки — только двойные кавычки
  • Значения: строки, числа, булевы, null, массивы, объекты
  • Нет функций, нет undefined, нет комментариев, нет trailing comma
  • На основе предыдущих уроков

  • «Объекты» — JSON похож на объектный литерал, но строже
  • «Массивы» — массивы в JSON: [1, 2, 3]
  • «Копирование объектов» — JSON как способ глубокого копирования
  • «try/catch» (впереди) — JSON.parse может выбросить ошибку
  • JSON.stringify — объект в строку (сериализация)

    const user = {
      name: 'Алексей',
      age: 28,
      isAdmin: false,
      tags: ['js', 'react'],
    }
    
    JSON.stringify(user)
    // '{"name":"Алексей","age":28,"isAdmin":false,"tags":["js","react"]}'
    
    // С форматированием (для читаемости / логов):
    JSON.stringify(user, null, 2)
    // {
    //   "name": "Алексей",
    //   "age": 28,
    //   ...
    // }

    JSON.parse — строка в объект (десериализация)

    const str = '{"name":"Алексей","age":28}'
    const obj = JSON.parse(str)
    console.log(obj.name)  // 'Алексей'
    console.log(typeof obj.age)  // 'number' — тип восстановлен

    Что теряется при stringify

    const data = {
      name: 'Тест',
      fn: function() {},    // функция — ИГНОРИРУЕТСЯ
      symbol: Symbol(),     // Symbol — ИГНОРИРУЕТСЯ
      undef: undefined,     // undefined — ИГНОРИРУЕТСЯ
      date: new Date(),     // Date → строка '2024-01-15T10:30:00.000Z'
      map: new Map(),       // Map → {}
      regexp: /abc/,        // RegExp → {}
    }
    
    console.log(JSON.stringify(data))
    // {"name":"Тест","date":"2024-01-15T10:30:00.000Z","map":{},"regexp":{}}

    replacer и reviver

    // replacer — управляет что включать в JSON
    const sensitive = { name: 'Иван', password: 'secret', age: 25 }
    const safe = JSON.stringify(sensitive, ['name', 'age'])  // только эти поля
    // '{"name":"Иван","age":25}'
    
    // reviver — восстанавливает типы при парсинге
    const withDate = '{"name":"Иван","createdAt":"2024-01-15T10:00:00.000Z"}'
    const parsed = JSON.parse(withDate, (key, value) => {
      if (key === 'createdAt') return new Date(value)  // строку → Date
      return value
    })
    console.log(parsed.createdAt instanceof Date)  // true

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

    // Простой способ — работает для простых данных без функций и Date
    const original = { user: { name: 'Алексей', scores: [10, 20, 30] } }
    const copy = JSON.parse(JSON.stringify(original))
    copy.user.name = 'Иван'
    console.log(original.user.name)  // 'Алексей' — не изменился
    
    // Лучший способ (ES2022):
    const copy = structuredClone(original)  // сохраняет Date, Map, Set

    Типичные ошибки

    Ошибка 1: JSON.parse без try/catch

    // Сломано — если строка невалидна, выбросит SyntaxError:
    const data = JSON.parse(localStorage.getItem('data'))  // может упасть!
    
    // Исправлено:
    function safeParse(str, fallback = null) {
      try {
        return JSON.parse(str)
      } catch {
        return fallback
      }
    }

    Ошибка 2: циклические ссылки

    // Сломано:
    const obj = { name: 'test' }
    obj.self = obj  // obj ссылается на себя
    JSON.stringify(obj)  // TypeError: Converting circular structure to JSON
    
    // Исправлено — structuredClone тоже не поможет, нужна обработка:
    // Используй библиотеку или вручную удали циклическую ссылку

    Ошибка 3: путаница с Date

    // Date → строка при stringify, обратно не восстанавливается автоматически:
    const obj = { date: new Date() }
    const str = JSON.stringify(obj)
    const back = JSON.parse(str)
    console.log(back.date instanceof Date)  // false — это строка!
    console.log(typeof back.date)           // 'string'
    
    // Исправлено — используй reviver или конвертируй явно:
    new Date(back.date)

    В реальных проектах

  • REST API: запросы и ответы в формате JSON (Content-Type: application/json)
  • localStorage: хранение объектов через stringify/parse
  • Конфигурационные файлы: package.json, tsconfig.json, .eslintrc.json
  • Логирование: console.log(JSON.stringify(data, null, 2)) для читаемых логов
  • WebSocket: передача сообщений как JSON-строк
  • Примеры

    Работа с API: сериализация, десериализация, безопасный парсинг

    // Симуляция работы с API ответом
    const rawApiResponse = `{
      "status": "ok",
      "data": {
        "products": [
          {"id": 1, "name": "Ноутбук", "price": 75000, "inStock": true},
          {"id": 2, "name": "Мышь",    "price": 1500,  "inStock": false},
          {"id": 3, "name": "Коврик",  "price": 500,   "inStock": true}
        ],
        "total": 3,
        "updatedAt": "2024-01-15T10:30:00.000Z"
      }
    }`
    
    // Парсинг с восстановлением дат
    function parseApiResponse(jsonStr) {
      try {
        return JSON.parse(jsonStr, (key, value) => {
          if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
            return new Date(value)
          }
          return value
        })
      } catch (e) {
        console.error('Ошибка парсинга:', e.message)
        return null
      }
    }
    
    const response = parseApiResponse(rawApiResponse)
    console.log(response.status)                        // 'ok'
    console.log(response.data.total)                    // 3
    console.log(response.data.updatedAt instanceof Date) // true
    
    // Фильтрация и сериализация для сохранения
    const inStock = response.data.products.filter(p => p.inStock)
    const forSave = JSON.stringify({ products: inStock }, null, 2)
    console.log(forSave)
    // {
    //   "products": [
    //     { "id": 1, "name": "Ноутбук", "price": 75000, "inStock": true },
    //     { "id": 3, "name": "Коврик",  "price": 500,   "inStock": true }
    //   ]
    // }
    
    // Безопасное хранение в "localStorage" (симуляция)
    const storage = {}
    function saveToStorage(key, data) {
      storage[key] = JSON.stringify(data)
    }
    function loadFromStorage(key, fallback = null) {
      try {
        const raw = storage[key]
        return raw != null ? JSON.parse(raw) : fallback
      } catch {
        return fallback
      }
    }
    
    saveToStorage('cart', inStock)
    const loaded = loadFromStorage('cart', [])
    console.log(loaded.length)         // 2
    console.log(loaded[0].name)        // 'Ноутбук'
    console.log(loadFromStorage('x', []).length)  // 0 — fallback

    Задание

    Создай мини-систему сохранения настроек приложения. Напиши функцию createSettingsStore(defaultSettings), которая возвращает объект с методами: save(settings) — сохраняет настройки в storage (симулируй через объект), load() — загружает и парсит, возвращая defaultSettings при ошибке, update(partial) — загружает текущие настройки, сливает с partial и сохраняет, reset() — удаляет сохранённые настройки.

    Подсказка

    save: storage[KEY] = JSON.stringify(settings). load: JSON.parse(raw). update: save({ ...current, ...partial }). Ключ spread сначала current, потом partial — чтобы partial перезаписал.

    Загружаем среду выполнения...
    Загружаем AI-помощника...