Модуль json — работа с JSON в Python

JSON (JavaScript Object Notation) — самый популярный текстовый формат обмена данными в современном вебе. На нём отвечают REST API, в нём хранятся конфиги, и именно в JSON ваш скрипт чаще всего получит данные от внешнего сервиса.

Стандартный модуль Python json умеет превращать Python-объекты в JSON-строку и обратно. Модуль входит в стандартную библиотеку — никаких установок не нужно:

import json

Зачем нужен JSON

JSON — это компромисс между читаемостью человеком и компактностью для машины. Пример JSON-документа:

{
    "id": 42,
    "name": "Айдос",
    "skills": ["Python", "Arduino"],
    "active": true,
    "city": null
}

Применения:

  • REST API. Запросы и ответы почти всегда в JSON.

  • Конфигурационные файлы. settings.json, package.json и сотни других.

  • Логи событий в современных системах мониторинга.

  • Сериализация состояния между запусками программы.


Соответствие типов Python ↔ JSON

Python           ->    JSON
----------------------------
dict             ->    object
list, tuple      ->    array
str              ->    string
int, float       ->    number
True             ->    true
False            ->    false
None             ->    null

И обратно при чтении. Обратите внимание: JSON не различает list и tuple — оба превратятся в JSON-array, а назад придут как list.


Сериализация: Python в JSON

Две функции: json.dumps (вернуть строку) и json.dump (записать в файл).

import json

data = {
    "name": "Айдос",
    "age": 21,
    "skills": ["Python", "Arduino"],
}

text = json.dumps(data)
print(text)
# {"name": "Айдос", "age": 21, ...}

Кириллица превратилась в Unicode-escape — это безопасно, но нечитаемо. Лечится параметром ensure_ascii=False:

text = json.dumps(data, ensure_ascii=False, indent=2)
print(text)

Вывод:

{
  "name": "Айдос",
  "age": 21,
  "skills": [
    "Python",
    "Arduino"
  ]
}

Полезные параметры:

  • indent — отступ для красивого вывода (обычно 2 или 4);

  • ensure_ascii=False — оставить кириллицу как есть;

  • sort_keys=True — отсортировать ключи (удобно для diff и тестов);

  • separators=(',', ':') — компактный формат без пробелов (минимальный размер).

Запись в файл:

with open('user.json', 'w', encoding='utf-8') as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

Десериализация: JSON в Python

Симметрично: json.loads (из строки) и json.load (из файла).

import json

text = '{"name": "Айдос", "age": 21}'
data = json.loads(text)
print(data['name'])  # Айдос
print(type(data))    # <class 'dict'>

Из файла:

with open('user.json', encoding='utf-8') as f:
    data = json.load(f)

Практический пример: ответ от API

Типичная задача — обратиться к веб-API и распарсить ответ:

import json
import urllib.request

url = 'https://api.github.com/repos/python/cpython'
with urllib.request.urlopen(url) as r:
    payload = json.load(r)

print('Звёзд:', payload['stargazers_count'])
print('Описание:', payload['description'])

Практический пример: конфиг приложения

Сохраняем настройки между запусками:

import json
from pathlib import Path

CONFIG = Path('config.json')

def load_config():
    if CONFIG.exists():
        with CONFIG.open(encoding='utf-8') as f:
            return json.load(f)
    return {'theme': 'light', 'lang': 'ru'}

def save_config(cfg):
    with CONFIG.open('w', encoding='utf-8') as f:
        json.dump(cfg, f, ensure_ascii=False, indent=2)

cfg = load_config()
cfg['theme'] = 'dark'
save_config(cfg)

Кастомные типы: default и object_hook

json не умеет сериализовать datetime, set, классы и другие нестандартные объекты. При попытке получите TypeError. Решение — параметр default:

import json
from datetime import datetime

def encode(obj):
    if isinstance(obj, datetime):
        return obj.isoformat()
    if isinstance(obj, set):
        return list(obj)
    raise TypeError(f'Не знаю, как сериализовать {type(obj)}')

data = {'created': datetime.now(), 'tags': {'python', 'json'}}
print(json.dumps(data, default=encode, ensure_ascii=False))

При обратном чтении используют object_hook — функцию, которая получает каждый словарь после разбора и может его преобразовать.


Подводные камни

Предупреждение

  • ``ensure_ascii=True`` по умолчанию. Кириллица будет escape’нута. Для человекочитаемых файлов всегда ставьте False.

  • Ключи словаря в JSON только строки. json.dumps({1: 'a'}) молча превратит ключ в "1".

  • ``True`` и ``False`` в JSON — без кавычек и с маленькой буквы. Ручная сборка JSON через str.format почти всегда заканчивается багом — используйте json.dumps.

  • NaN и Infinity — не валидный JSON, хотя Python их пишет. Парсер на другой стороне может упасть. Установите allow_nan=False, чтобы получить ошибку на своей стороне.

  • Безопасность. json.load безопасен сам по себе, но не доверяйте неизвестным размерам — большой JSON может съесть всю память.


JSON Lines (JSONL)

Часто данные хранят в формате JSON Lines — по одному JSON-объекту на строку:

{"id": 1, "name": "Айдос"}
{"id": 2, "name": "Гульнара"}

Это удобно для логов и потоковой обработки больших файлов:

import json

with open('events.jsonl', encoding='utf-8') as f:
    for line in f:
        event = json.loads(line)
        print(event['id'])

Смотрите также


Примечание

Лицензия и источники

Часть материала адаптирована из официальной документации Python (https://docs.python.org/3/library/json.html), доступной под Python Software Foundation License Version 2 (PSF License). Адаптация, переработка, оригинальные примеры и пояснения — © AlashEd Wiki.