раздел 04

JSON: структура и вложенность

JSON - самый важный формат в вебе. На нём общаются почти все API, в нём хранят настройки, конфиги и данные приложений. Если CSV - это плоская таблица, то JSON - это дерево: внутри значений могут лежать другие значения, списки и объекты. Именно это делает его таким универсальным.

Что это

JSON (JavaScript Object Notation) описывает данные парами «ключ - значение» и списками. Выглядит почти как то, как вы устно описали бы объект.

{
  "name": "Анна",
  "age": 28,
  "city": "Москва",
  "phones": ["+7900...", "+7901..."],
  "active": true
}

Здесь сразу видно то, чего не умеет CSV: phones - это список из нескольких значений внутри одной записи. И типы честные: age - число, active - логическое значение, name - строка.

Зачем это

JSON берут, когда:

  • у данных есть структура и вложенность (объект внутри объекта, списки);
  • важны типы: число, строка, true/false, null;
  • данные передаются между программами по сети (это язык по умолчанию для API);
  • нужно хранить конфиг или настройки в читаемом виде.

Практически весь современный веб держится на JSON: фронтенд просит данные у сервера и получает JSON, отправляет форму - тоже JSON.

Как это работает

Во всех языках есть встроенное превращение JSON в объекты программы и обратно. Это две операции: «разобрать текст в объект» (parse) и «превратить объект в текст» (stringify / dump).

import json

data = {
    "name": "Анна",
    "tasks": ["купить молоко", "позвонить маме"],
    "done": False,
}

# объект -> текст и запись в файл
with open("data.json", "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

# чтение файла -> объект
with open("data.json", "r", encoding="utf-8") as f:
    loaded = json.load(f)

print(loaded["tasks"][0])   # купить молоко

Два полезных параметра при записи в Python:

  • ensure_ascii=False - чтобы русский текст сохранялся как есть, а не как Ан...;
  • indent=2 - чтобы файл был с отступами и читался глазами.

В JavaScript то же самое ещё короче:

const data = { name: "Анна", tasks: ["купить молоко"], done: false };

const text = JSON.stringify(data, null, 2); // объект -> текст
const obj = JSON.parse(text);               // текст -> объект

Пример: JSON как маленькая база данных

Для прототипа JSON-файл часто заменяет базу. Читаем весь файл, меняем в памяти, пишем обратно.

import json

def add_task(text, path="tasks.json"):
    try:
        with open(path, "r", encoding="utf-8") as f:
            tasks = json.load(f)
    except FileNotFoundError:
        tasks = []                       # первый запуск

    tasks.append({"text": text, "done": False})

    with open(path, "w", encoding="utf-8") as f:
        json.dump(tasks, f, ensure_ascii=False, indent=2)

add_task("сдать отчёт")

Для десятков и сотен записей это отлично работает. Тысячи и десятки тысяч - уже тяжело: каждый раз перечитывать и переписывать весь файл становится медленно, и одновременная запись из двух мест всё ломает. Тогда пора в базу данных.

Где у JSON потолок

JSON хранит данные целиком одним куском. Чтобы изменить одну запись, нужно прочитать и переписать весь файл. Нет встроенного поиска - ищете перебором в коде. Нет защиты от одновременной записи. Всё это решает база данных - раздел 05.

Антипаттерны

  • Хранить в одном JSON-файле гигантские объёмы. Перезапись всего файла ради одной правки на больших данных - это медленно. Сигнал к базе.
  • Забыть ensure_ascii=False в Python - и получить нечитаемые А вместо русских букв (работать будет, но глазами не прочитать).
  • Лишняя запятая в конце. В JSON нельзя ставить запятую после последнего элемента - это самая частая синтаксическая ошибка. Подробнее на странице про вложенность.

Подразделы