JSON в Python — парсинг, создание и работа с файлами

Коротко

JSON (JavaScript Object Notation) — текстовый формат обмена данными. В Python модуль json из стандартной библиотеки конвертирует JSON в словари и обратно. Аналитику JSON нужен постоянно: API-ответы, конфиги, логи, выгрузки из систем аналитики. На собеседованиях спрашивают про парсинг JSON, обработку вложенных структур и разницу между loads/dumps и load/dump.

Базовые операции

import json

# JSON-строка → Python-объект
data = json.loads('{"name": "Иван", "age": 28}')
print(data)          # {'name': 'Иван', 'age': 28}
print(type(data))    # <class 'dict'>
print(data['name'])  # Иван

# Python-объект → JSON-строка
json_str = json.dumps(data)
print(json_str)      # {"name": "\u0418\u0432\u0430\u043d", "age": 28}

# С кириллицей и отступами
json_str = json.dumps(data, ensure_ascii=False, indent=2)
print(json_str)
# {
#   "name": "Иван",
#   "age": 28
# }
  • json.loads(string) — parse JSON-строки (load string)
  • json.dumps(object) — создать JSON-строку (dump string)
  • ensure_ascii=False — не экранировать кириллицу
  • indent=2 — форматирование с отступами

Чтение и запись файлов

import json

# Чтение JSON-файла
with open('config.json', 'r', encoding='utf-8') as f:
    config = json.load(f)

# Запись в JSON-файл
with open('output.json', 'w', encoding='utf-8') as f:
    json.dump(config, f, ensure_ascii=False, indent=2)
  • json.load(file) — читает из файлового объекта
  • json.dump(object, file) — пишет в файловый объект

Запомните: с s на конце (loads, dumps) — работа со строками. Без s (load, dump) — работа с файлами.

Типы данных: JSON ↔ Python

JSON Python
object {} dict
array [] list
string "" str
number (int) int
number (float) float
true / false True / False
null None
data = json.loads('''
{
    "users": [
        {"id": 1, "active": true, "score": null},
        {"id": 2, "active": false, "score": 95.5}
    ],
    "total": 2
}
''')

print(data['users'][0]['active'])  # True (bool)
print(data['users'][0]['score'])   # None
print(data['total'])               # 2 (int)

JSON true → Python True, nullNone. Обратное преобразование работает автоматически.

Вложенные структуры

API часто возвращают глубоко вложенные JSON. Навигация по ним — базовый навык аналитика.

response = {
    "data": {
        "users": [
            {
                "id": 1,
                "profile": {
                    "name": "Анна",
                    "contacts": {"email": "anna@example.com"}
                },
                "orders": [
                    {"id": 101, "amount": 5000},
                    {"id": 102, "amount": 3200}
                ]
            }
        ]
    },
    "meta": {"page": 1, "total": 100}
}

# Навигация по вложенным ключам
user = response['data']['users'][0]
email = user['profile']['contacts']['email']
first_order = user['orders'][0]['amount']

# Безопасный доступ через .get()
phone = user['profile']['contacts'].get('phone', 'не указан')

.get(key, default) — не выбросит KeyError, если ключа нет. Для глубоко вложенных структур это безопаснее прямого доступа.

Работа с API-ответами

import json
import urllib.request

url = 'https://api.example.com/users?page=1'
req = urllib.request.Request(url)

with urllib.request.urlopen(req) as response:
    data = json.loads(response.read().decode('utf-8'))

# Или с библиотекой requests (рекомендуется)
import requests

response = requests.get('https://api.example.com/users', params={'page': 1})
data = response.json()  # автоматический парсинг

response.json() в requests — шорткат для json.loads(response.text).

JSON в pandas

import pandas as pd

# Из JSON-файла
df = pd.read_json('data.json')

# Из JSON-строки
json_str = '[{"name": "Иван", "age": 28}, {"name": "Анна", "age": 32}]'
df = pd.read_json(json_str)

# Из списка словарей (после парсинга API)
data = [
    {'user_id': 1, 'revenue': 5000},
    {'user_id': 2, 'revenue': 3200}
]
df = pd.DataFrame(data)

# DataFrame → JSON
df.to_json('output.json', orient='records', force_ascii=False)

Для вложенных JSON используйте pd.json_normalize():

data = [
    {'id': 1, 'profile': {'name': 'Иван', 'city': 'Москва'}, 'score': 85},
    {'id': 2, 'profile': {'name': 'Анна', 'city': 'Питер'}, 'score': 92}
]

df = pd.json_normalize(data)
print(df.columns)
# Index(['id', 'score', 'profile.name', 'profile.city'])

json_normalize «разворачивает» вложенные словари в плоские столбцы — то, что нужно для анализа.

JSON Lines (JSONL)

Формат, где каждая строка файла — отдельный JSON-объект. Часто используется для логов и стриминга данных.

# Чтение JSONL
events = []
with open('events.jsonl', 'r') as f:
    for line in f:
        events.append(json.loads(line))

# Запись JSONL
with open('output.jsonl', 'w') as f:
    for event in events:
        f.write(json.dumps(event, ensure_ascii=False) + '\n')

# Через pandas
df = pd.read_json('events.jsonl', lines=True)
df.to_json('output.jsonl', orient='records', lines=True)

Параметр lines=True в pandas — для чтения и записи JSONL.

Обработка ошибок

import json

# Невалидный JSON
try:
    data = json.loads('{"name": "broken}')
except json.JSONDecodeError as e:
    print(f'Ошибка парсинга: {e}')
    # Ошибка парсинга: Unterminated string starting at: line 1 column 10

# Несериализуемый объект
from datetime import datetime

data = {'created_at': datetime.now()}
try:
    json.dumps(data)
except TypeError as e:
    print(f'Ошибка: {e}')
    # Object of type datetime is not JSON serializable

datetime, set, bytes — не сериализуются в JSON. Решение — кастомный сериализатор:

def custom_serializer(obj):
    if isinstance(obj, datetime):
        return obj.isoformat()
    if isinstance(obj, set):
        return list(obj)
    raise TypeError(f'Not serializable: {type(obj)}')

data = {'created_at': datetime.now(), 'tags': {'sql', 'python'}}
json_str = json.dumps(data, default=custom_serializer, ensure_ascii=False)

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

Путают loads/dumps и load/dump. С s — строки, без s — файлы. json.load(string) выбросит ошибку — нужен файловый объект.

Забывают ensure_ascii=False. По умолчанию кириллица экранируется в \uXXXX. Для читаемого вывода — ensure_ascii=False.

Одинарные кавычки. JSON требует двойных кавычек. {'key': 'value'} — невалидный JSON. {"key": "value"} — валидный. Python словари используют любые кавычки, JSON — только двойные.

Trailing comma. {"a": 1, "b": 2,} — невалидный JSON (висячая запятая). В Python dict это допустимо, в JSON — нет.

Вопросы с собеседований

-- Чем отличается json.loads от json.load? -- loads (load string) принимает строку, load принимает файловый объект. Аналогично dumps и dump для сериализации.

-- Как обработать вложенный JSON в pandas? -- pd.json_normalize(data) разворачивает вложенные словари в плоские столбцы. Для массивов внутри объектов можно указать record_path и meta.

-- Что делать, если в JSON есть datetime? -- JSON не поддерживает datetime нативно. При сериализации передать default-функцию, которая конвертирует в ISO-формат. При десериализации — парсить строку обратно в datetime.

-- Как прочитать JSONL-файл? -- Построчно: for line in f: json.loads(line). Через pandas: pd.read_json('file.jsonl', lines=True). JSONL — каждая строка файла — отдельный JSON-объект.


Потренируйтесь решать задачи — откройте тренажёр с 1500+ вопросами для подготовки к собеседованиям аналитиков.

FAQ

JSON vs CSV — когда что использовать?

CSV — для табличных данных с фиксированной структурой. JSON — для вложенных, иерархических данных и API-ответов. CSV компактнее и быстрее читается pandas. JSON гибче и поддерживает любую вложенность.

Как работать с большими JSON-файлами?

Для файлов, не помещающихся в память, используйте потоковый парсинг (ijson библиотека) или JSONL-формат с построчным чтением. В pandas — pd.read_json('file.jsonl', lines=True, chunksize=1000).

JSON в SQL — это возможно?

Да. PostgreSQL, MySQL 5.7+, ClickHouse поддерживают тип JSONB/JSON. Можно хранить и запрашивать JSON прямо в БД: SELECT data->>'name' FROM users.

Как тренироваться

Задачи на работу с JSON, API и структурами данных — в тренажёре Карьерник. Больше вопросов по Python — в разделе с примерами.