JSON to Python
Генерація Python dataclasses з JSON
Введення JSON
Виведення Python
Що таке конвертація JSON у Python dataclasses?
Конвертація JSON у Python dataclasses перетворює сирий JSON-об'єкт на набір визначень dataclass з точними анотаціями типів. Модуль dataclasses, представлений у PEP 557 (Python 3.7), автоматично генерує методи __init__, __repr__ та __eq__ з анотованих полів класу. Коли ви працюєте з JSON API, конфігураційними файлами або чергами повідомлень, dataclasses надають вашим даним типізовану структуру, яку редактори та засоби перевірки типів на кшталт mypy можуть верифікувати під час розробки.
Метод json.loads() у Python повертає звичайні словники та списки. Це працює, але не містить інформації про типи: помилка в назві ключа повертає None замість виключення, а редактор не може автодоповнювати імена полів. Dataclasses вирішують це, відображаючи кожен ключ JSON на іменоване типізоване поле. Вкладені JSON-об'єкти стають окремими визначеннями dataclass, масиви перетворюються на анотації List[T], а null-значення — на Optional[T] з типовим значенням None.
Написання таких визначень вручну — це механічна робота. Потрібно проаналізувати JSON, визначити тип кожного поля за його значенням, перетворити ключі з camelCase або snake_case на конвенції Python та обробити крайні випадки, як-от поля, що допускають null, і масиви зі змішаними типами. Конвертер виконує все це за мілісекунди. Ви вставляєте JSON, отримуєте коректний код dataclass і рухаєтесь далі.
Навіщо використовувати конвертер JSON to Python?
Ручне перетворення JSON-структур у визначення Python-класів означає здогадування про типи з тестових даних, впорядкування полів так, щоб обов'язкові йшли перед опціональними, та оновлення всього при зміні API. Конвертер усуває це тертя.
Сценарії використання JSON to Python
Таблиця відповідності типів JSON та Python
Кожне JSON-значення відображається на конкретну анотацію типу Python. У таблиці нижче показано, як конвертер перекладає кожен JSON-тип — як із синтаксисом модуля typing (Python 3.7+), так і з вбудованим синтаксисом, доступним починаючи з Python 3.10.
| Тип JSON | Приклад | Python (typing) | Python 3.10+ |
|---|---|---|---|
| string | "hello" | str | str |
| number (integer) | 42 | int | int |
| number (float) | 3.14 | float | float |
| boolean | true | bool | bool |
| null | null | Optional[str] | str | None |
| object | {"k": "v"} | @dataclass class | nested model |
| array of strings | ["a", "b"] | List[str] | list[str] |
| array of objects | [{"id": 1}] | List[Item] | list[Item] |
| mixed array | [1, "a"] | List[Any] | list[Any] |
Довідник декоратора dataclass
Декоратор @dataclass приймає кілька параметрів, що змінюють поведінку згенерованого класу. Цей довідник охоплює параметри, найбільш актуальні при роботі з даними, отриманими з JSON.
| Декоратор / Поле | Поведінка | Коли використовувати |
|---|---|---|
| @dataclass | Generates __init__, __repr__, __eq__ from field annotations | Standard dataclasses |
| @dataclass(frozen=True) | Makes instances immutable (hashable, no attribute reassignment) | Config objects, dict keys |
| @dataclass(slots=True) | Uses __slots__ for lower memory and faster attribute access | Python 3.10+, large datasets |
| @dataclass(kw_only=True) | All fields require keyword arguments in __init__ | Python 3.10+, many fields |
| field(default_factory=list) | Sets a mutable default without sharing state between instances | List/dict/set defaults |
dataclass проти Pydantic проти TypedDict
У Python є три поширені способи визначення типізованих структур з JSON. Кожен підходить для різних випадків використання. Dataclasses — це варіант зі стандартної бібліотеки без зовнішніх залежностей. Pydantic додає валідацію під час виконання. TypedDict анотує звичайні словники без створення нового класу.
Приклади коду
Ці приклади показують, як використовувати згенеровані dataclasses у Python, як отримувати їх програмно через JavaScript, а також як застосовувати альтернативні підходи на кшталт Pydantic та CLI-інструментів.
from dataclasses import dataclass
from typing import List, Optional
import json
@dataclass
class Address:
street: str
city: str
zip: str
@dataclass
class User:
id: int
name: str
email: str
active: bool
score: float
address: Address
tags: List[str]
metadata: Optional[str] = None
raw = '{"id":1,"name":"Alice","email":"alice@example.com","active":true,"score":98.5,"address":{"street":"123 Main St","city":"Springfield","zip":"12345"},"tags":["admin","user"],"metadata":null}'
data = json.loads(raw)
# Reconstruct nested objects manually
addr = Address(**data["address"])
user = User(**{**data, "address": addr})
print(user.name) # -> Alice
print(user.address) # -> Address(street='123 Main St', city='Springfield', zip='12345')// Minimal JSON-to-Python-dataclass generator in JS
function jsonToPython(obj, name = "Root") {
const classes = [];
function infer(val, fieldName) {
if (val === null) return "Optional[str]";
if (typeof val === "string") return "str";
if (typeof val === "number") return Number.isInteger(val) ? "int" : "float";
if (typeof val === "boolean") return "bool";
if (Array.isArray(val)) {
const first = val.find(v => v !== null);
return first ? `List[${infer(first, fieldName + "Item")}]` : "List[Any]";
}
if (typeof val === "object") {
const clsName = fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
build(val, clsName);
return clsName;
}
return "Any";
}
function build(obj, cls) {
const fields = Object.entries(obj).map(([k, v]) => ` ${k}: ${infer(v, k)}`);
classes.push(`@dataclass\nclass ${cls}:\n${fields.join("\n")}`);
}
build(obj, name);
return classes.join("\n\n");
}
const data = { id: 1, name: "Alice", scores: [98, 85] };
console.log(jsonToPython(data, "User"));
// @dataclass
// class User:
// id: int
// name: str
// scores: List[int]from pydantic import BaseModel
from typing import List, Optional
class Address(BaseModel):
street: str
city: str
zip: str
class User(BaseModel):
id: int
name: str
email: str
active: bool
score: float
address: Address
tags: List[str]
metadata: Optional[str] = None
# Pydantic parses and validates JSON in one step
raw = '{"id":1,"name":"Alice","email":"alice@example.com","active":true,"score":98.5,"address":{"street":"123 Main St","city":"Springfield","zip":"12345"},"tags":["admin","user"],"metadata":null}'
user = User.model_validate_json(raw)
print(user.name) # -> Alice
print(user.model_dump_json()) # -> re-serializes to JSON# Install the generator
pip install datamodel-code-generator
# Generate dataclasses from a JSON file
datamodel-codegen --input data.json --output models.py --output-model-type dataclasses.dataclass
# Generate Pydantic models instead
datamodel-codegen --input data.json --output models.py
# From a JSON string via stdin
echo '{"id": 1, "name": "Alice", "tags": ["admin"]}' | \
datamodel-codegen --output-model-type dataclasses.dataclass
# Output:
# @dataclass
# class Model:
# id: int
# name: str
# tags: List[str]