JSON, CSV и сериализация
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
Напишите 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"
import json
from datetime import date, datetime
from decimal import Decimal
def _default(value):
if isinstance(value, (date, datetime)):
return value.isoformat()
if isinstance(value, Decimal):
return str(value)
raise TypeError(f"Не умею сериализовать {type(value).__name__}")
def to_json(obj):
return json.dumps(obj, default=_default, ensure_ascii=False, sort_keys=True)
Напишите 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}
import csv, io
def csv_total(text):
reader = csv.DictReader(io.StringIO(text))
return {row["name"]: float(row["qty"]) * float(row["price"]) for row in reader}
Во что превращается значение Python при json.dumps?
| объект {} | массив [] | строка | null | TypeError | |
|---|---|---|---|---|---|
{"a": 1} |
|||||
(1, 2) |
|||||
None |
|||||
{1, 2} |
|||||
datetime.now() |
|||||
ключ 1 в {1: "a"} |
Почему нельзя делать pickle.loads() для данных, пришедших от пользователя?
Зачем при открытии CSV-файла указывать newline=""?
Войдите, чтобы отмечать пройденные уроки.