JSON, CSV и сериализация

⏱ 35 мин · 🧩 заданий: 5

JSON

JSON — основной формат обмена данными в вебе: API, конфиги, сообщения в очередях.

Python JSON
dict объект {} (ключи — только строки)
list, tuple массив []
str строка
int, float число
True / False true / false
None null
import json

order = {"id": 42, "клиент": "Анна", "товары": ["чай", "кофе"], "оплачен": True, "скидка": None}

text = json.dumps(order)                                   # объект → строка
print(text)
print(json.dumps(order, ensure_ascii=False, indent=2))     # кириллица как есть, с отступами

data = json.loads('{"id": 1, "tags": ["a", "b"], "price": 9.5, "active": false}')   # строка → объект
print(data, type(data["tags"]))

print(json.loads(json.dumps({1: "a", "t": (1, 2)})))       # ключ стал строкой, кортеж — списком
Функция Что делает
json.dumps(obj) объект → строка
json.loads(s) строка (или bytes) → объект
json.dump(obj, f) объект → файл
json.load(f) файл → объект
import json, tempfile
from pathlib import Path

path = Path(tempfile.mkdtemp()) / "config.json"
with open(path, "w", encoding="utf-8") as f:
    json.dump({"debug": True, "db": {"host": "localhost", "port": 5432}}, f, ensure_ascii=False, indent=2)

with open(path, encoding="utf-8") as f:
    config = json.load(f)
print(config["db"]["port"])

try:
    json.loads("{'single': 'quotes'}")                     # в JSON только двойные кавычки
except json.JSONDecodeError as e:
    print("JSONDecodeError:", e)

Свои типы: default и object_hook

import json
from dataclasses import dataclass, asdict
from datetime import datetime, date
from decimal import Decimal
from uuid import UUID, uuid4

@dataclass
class Item:
    title: str
    price: Decimal

def encode(value):                       # вызывается для всего, что json не знает
    if isinstance(value, (datetime, date)):
        return value.isoformat()
    if isinstance(value, (Decimal, UUID)):
        return str(value)
    if hasattr(value, "__dataclass_fields__"):
        return asdict(value)
    raise TypeError(f"{type(value).__name__} не сериализуется в JSON")

payload = {"id": UUID("12345678-1234-5678-1234-567812345678"),
           "created": datetime(2026, 10, 25, 14, 30), "item": Item("Книга", Decimal("590.00"))}
text = json.dumps(payload, default=encode, ensure_ascii=False)
print(text)

def decode(obj):                         # вызывается для каждого словаря при чтении
    if "created" in obj:
        obj["created"] = datetime.fromisoformat(obj["created"])
    return obj

back = json.loads(text, object_hook=decode, parse_float=Decimal)
print(type(back["created"]).__name__, back["item"])

В реальных проектах эту работу берут на себя Pydantic (model_dump_json, model_validate_json) или быстрые библиотеки orjson/msgspec.

CSV

CSV — таблицы в текстовом виде: выгрузки из Excel, отчёты, импорт данных. Не парсите CSV через split(","): значения могут содержать запятые, кавычки и переносы строк.

import csv, io

raw = 'name,city,salary\nАнна,"Москва, центр",120000\nБоря,Казань,95000\n"Вера ""Ви""",Сочи,110000\n'

print(raw.splitlines()[1].split(","))           # наивный split ломается на запятой в кавычках

for row in csv.reader(io.StringIO(raw)):
    print(row)

for row in csv.DictReader(io.StringIO(raw)):    # строка → словарь по заголовку
    print(row["name"], int(row["salary"]) // 1000, "тыс.")

Запись

import csv, tempfile
from pathlib import Path

rows = [{"id": 1, "name": "Анна", "note": "любит, кофе"}, {"id": 2, "name": "Боря", "note": 'сказал "привет"'}]
path = Path(tempfile.mkdtemp()) / "users.csv"

with open(path, "w", newline="", encoding="utf-8") as f:     # newline="" — обязательно для csv
    writer = csv.DictWriter(f, fieldnames=["id", "name", "note"])
    writer.writeheader()
    writer.writerows(rows)

print(path.read_text(encoding="utf-8"))

with open(path, newline="", encoding="utf-8") as f:
    print([r["note"] for r in csv.DictReader(f)])           # всё вернулось как было

CSV для Excel

Русский Excel ожидает разделитель ; и кодировку с BOM: open(path, "w", newline="", encoding="utf-8-sig") и csv.writer(f, delimiter=";"). Все значения в CSV — строки: числа и даты нужно преобразовывать самим.

pickle

pickle сохраняет почти любой объект Python в байты и восстанавливает его:

import pickle
from collections import Counter

data = {"counter": Counter("hello"), "tuple": (1, 2), "set": {3, 4}}
blob = pickle.dumps(data)
print(len(blob), "байт")
print(pickle.loads(blob))

pickle и недоверенные данные

pickle.loads может выполнить произвольный код — объект сам описывает, как его восстановить, в том числе вызовом os.system. Никогда не распаковывайте pickle из сети, от пользователя или из непроверенных файлов. Для обмена данными — JSON. pickle уместен для кешей и передачи данных между своими процессами (multiprocessing использует его внутри).

Другие форматы

Формат Модуль Когда
JSON json (stdlib) API, обмен данными
CSV csv (stdlib) таблицы, Excel, выгрузки
TOML tomllib (stdlib, только чтение) конфиги, pyproject.toml
YAML PyYAML (yaml.safe_load!) конфиги, Kubernetes, CI
XML xml.etree.ElementTree, lxml (defusedxml для чужих данных) старые интеграции, SOAP
MessagePack, Protobuf msgpack, protobuf компактный бинарный обмен, gRPC
Parquet pyarrow, pandas аналитика, большие таблицы
Excel openpyxl, pandas отчёты для людей
import tomllib

config = tomllib.loads('''
[project]
name = "shop"
version = "1.0.0"
dependencies = ["fastapi>=0.110"]

[tool.ruff]
line-length = 100
''')
print(config["project"]["name"], config["tool"]["ruff"]["line-length"])

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

«Как сериализовать в JSON объект с датой?» — Передать в json.dumps параметр default=функция, которая превращает datetime в строку ISO (value.isoformat()), или наследовать json.JSONEncoder и переопределить default. В проектах это обычно делает Pydantic.

Итог

  • json.dumps/loads — строки, json.dump/load — файлы; ensure_ascii=False, indent=2.
  • Неизвестные типы — через default=, обратное преобразование — object_hook.
  • CSV — только через модуль csv (DictReader/DictWriter), файл открывать с newline="".
  • pickle — только для своих доверенных данных.
  • tomllib для TOML, yaml.safe_load для YAML.

Практика решено 0 из 5

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Программа на Python

Напишите to_json(obj), которая сериализует в JSON словарь, содержащий даты (datetime.date и datetime.datetime) и Decimal: даты превращаются в строки ISO ("2026-10-25"), Decimal — в строку ("19.90"). Кириллица — без экранирования, ключи — по алфавиту.

Какие тесты проверяют код (3)
assert to_json({"товар": "чай", "цена": Decimal("19.90"), "дата": date(2026, 10, 25)}) == '{"дата": "2026-10-25", "товар": "чай", "цена": "19.90"}'
assert to_json({"t": datetime(2026, 1, 2, 3, 4, 5)}) == '{"t": "2026-01-02T03:04:05"}'
try:
    to_json({"x": {1, 2}})
    ok = False
except TypeError:
    ok = True
assert ok, "Для неизвестных типов — TypeError"

Задание 2 Программа на Python

Напишите csv_total(text): на вход — CSV-текст с заголовком name,qty,price (числа могут быть дробными, в названиях встречаются запятые в кавычках). Верните словарь {название: qty * price}. Используйте csv.DictReader и io.StringIO.

Какие тесты проверяют код (2)
assert csv_total("name,qty,price\nчай,2,100\nкофе,1,250.5\n") == {"чай": 200.0, "кофе": 250.5}
assert csv_total('name,qty,price\n"Ручка, синяя",3,10\n') == {"Ручка, синяя": 30.0}
Задание 3 Заполнить таблицу

Во что превращается значение Python при json.dumps?

объект {}массив []строкаnullTypeError
{"a": 1}
(1, 2)
None
{1, 2}
datetime.now()
ключ 1 в {1: "a"}
Отметьте ячейку — «+», пустая — «−».
Задание 4 Один вариант ответа

Почему нельзя делать pickle.loads() для данных, пришедших от пользователя?

Задание 5 Один вариант ответа

Зачем при открытии CSV-файла указывать newline=""?

Войдите, чтобы отмечать пройденные уроки.