4. Nested Types
What You'll Learn
- Use nested dataclasses
- Work with Union and Optional types
- Handle lists and dicts with type parameters
Simple Nested Structure
nested_simple.py
from dataclasses import dataclass
from jsonrpc import JSONRPC, Method
@dataclass
class Address:
street: str
city: str
country: str
postal_code: str
@dataclass
class CreateAddressParams:
user_id: int
address: Address
@dataclass
class AddressSavedResult:
user_id: int
street: str
city: str
class SaveAddress(Method):
def execute(self, params: CreateAddressParams) -> AddressSavedResult:
return AddressSavedResult(
user_id=params.user_id,
street=params.address.street,
city=params.address.city,
)
rpc = JSONRPC(version='2.0')
rpc.register('save_address', SaveAddress())
Request:
nested_request.json
{
"jsonrpc": "2.0",
"method": "save_address",
"params": {
"user_id": 42,
"address": {
"street": "123 Main St",
"city": "New York",
"country": "USA",
"postal_code": "10001"
}
},
"id": 1
}
Response:
nested_response.json
{
"jsonrpc": "2.0",
"result": {
"user_id": 42,
"street": "123 Main St",
"city": "New York"
},
"id": 1
}
Deep Nesting (3 Levels)
deep_nesting.py
from dataclasses import dataclass
from jsonrpc import JSONRPC, Method
@dataclass
class Contact:
email: str
phone: str
@dataclass
class Address:
street: str
city: str
contact: Contact
@dataclass
class CompanyInfo:
name: str
address: Address
employees: int
@dataclass
class RegisterCompanyParams:
company: CompanyInfo
@dataclass
class RegisterCompanyResult:
company_id: int
name: str
location: str
contact_email: str
class RegisterCompany(Method):
def execute(self, params: RegisterCompanyParams) -> RegisterCompanyResult:
return RegisterCompanyResult(
company_id=999,
name=params.company.name,
location=params.company.address.city,
contact_email=params.company.address.contact.email,
)
rpc = JSONRPC(version='2.0')
rpc.register('register_company', RegisterCompany())
Request:
deep_nested_request.json
{
"jsonrpc": "2.0",
"method": "register_company",
"params": {
"company": {
"name": "Acme Corp",
"address": {
"street": "456 Business Ave",
"city": "San Francisco",
"contact": {
"email": "info@acme.com",
"phone": "+1-555-0100"
}
},
"employees": 150
}
},
"id": 1
}
Response:
deep_nested_response.json
{
"jsonrpc": "2.0",
"result": {
"company_id": 999,
"name": "Acme Corp",
"location": "San Francisco",
"contact_email": "info@acme.com"
},
"id": 1
}
Union and Optional Types
union_optional.py
from dataclasses import dataclass
from jsonrpc import JSONRPC, Method
@dataclass
class ProcessPaymentParams:
amount: float
currency: str
method: str # "card" | "paypal" | "crypto"
card_number: str | None = None
paypal_email: str | None = None
crypto_address: str | None = None
@dataclass
class PaymentResult:
transaction_id: str
amount: float
currency: str
status: str
class ProcessPayment(Method):
def execute(self, params: ProcessPaymentParams) -> PaymentResult:
return PaymentResult(
transaction_id='tx_123',
amount=params.amount,
currency=params.currency,
status='completed',
)
rpc = JSONRPC(version='2.0')
rpc.register('process_payment', ProcessPayment())
Request (card):
payment_card_request.json
{
"jsonrpc": "2.0",
"method": "process_payment",
"params": {
"amount": 99.99,
"currency": "USD",
"method": "card",
"card_number": "4111111111111111"
},
"id": 1
}
Response:
payment_response.json
{
"jsonrpc": "2.0",
"result": {
"transaction_id": "tx_123",
"amount": 99.99,
"currency": "USD",
"status": "completed"
},
"id": 1
}
Lists and Dicts
collections.py
from dataclasses import dataclass
from jsonrpc import JSONRPC, Method
@dataclass
class Item:
id: int
name: str
price: float
@dataclass
class CreateOrderParams:
items: list[Item]
metadata: dict[str, str | int]
discounts: list[float]
@dataclass
class OrderResult:
order_id: str
items_count: int
total: float
metadata: dict[str, str | int]
class CreateOrder(Method):
def execute(self, params: CreateOrderParams) -> OrderResult:
total = sum(item.price for item in params.items)
discount_total = sum(params.discounts)
return OrderResult(
order_id='ORD-001',
items_count=len(params.items),
total=round(total - discount_total, 2),
metadata=params.metadata,
)
rpc = JSONRPC(version='2.0')
rpc.register('create_order', CreateOrder())
Request:
order_request.json
{
"jsonrpc": "2.0",
"method": "create_order",
"params": {
"items": [
{"id": 1, "name": "Laptop", "price": 999.99},
{"id": 2, "name": "Mouse", "price": 29.99}
],
"metadata": {
"customer_id": "12345",
"source": "web",
"priority": 1
},
"discounts": [50.0, 10.0]
},
"id": 1
}
Response:
order_response.json
{
"jsonrpc": "2.0",
"result": {
"order_id": "ORD-001",
"items_count": 2,
"total": 969.98,
"metadata": {
"customer_id": "12345",
"source": "web",
"priority": 1
}
},
"id": 1
}
Complex Type Combinations
complex_types.py
from dataclasses import dataclass
from typing import Literal
from jsonrpc import JSONRPC, Method
@dataclass
class ConfigParams:
mode: Literal["development", "production", "testing"]
features: dict[str, bool]
allowed_ips: list[str] | None = None
max_retries: int = 3
@dataclass
class SecurityConfig:
ip_whitelist: list[str]
max_retries: int
@dataclass
class ConfigResult:
mode: str
features_enabled: list[str]
security: SecurityConfig
class UpdateConfig(Method):
def execute(self, params: ConfigParams) -> ConfigResult:
return ConfigResult(
mode=params.mode,
features_enabled=[k for k, v in params.features.items() if v],
security=SecurityConfig(
ip_whitelist=params.allowed_ips or [],
max_retries=params.max_retries,
),
)
rpc = JSONRPC(version='2.0')
rpc.register('update_config', UpdateConfig())
Request:
config_request.json
{
"jsonrpc": "2.0",
"method": "update_config",
"params": {
"mode": "production",
"features": {
"analytics": true,
"beta_features": false,
"logging": true
},
"allowed_ips": ["192.168.1.1", "10.0.0.1"]
},
"id": 1
}
Response:
config_response.json
{
"jsonrpc": "2.0",
"result": {
"mode": "production",
"features_enabled": ["analytics", "logging"],
"security": {
"ip_whitelist": ["192.168.1.1", "10.0.0.1"],
"max_retries": 3
}
},
"id": 1
}
Key Points
- Nested dataclasses: Automatic recursive validation
- Union types:
str | intfor multiple allowed types - Optional:
str | Nonefor nullable fields - Lists:
list[Item]validates each element - Dicts:
dict[str, int]validates keys and values - Literal:
Literal["a", "b"]restricts to specific values
Nested results too
Result dataclasses can be nested just like params — the library serializes them recursively.
What's Next?
→ Context - Authentication and request context