ESC

Type to search...

Serialization

EVOID provides the interface. You bring your own library.

The Principle

IOP is about Intent + Pipeline. Serialization is HOW you move data in/out — that’s your choice, not EVOID’s.

EVOID defines one protocol:

class Serializer(Protocol):
    def encode(self, data: Any) -> bytes: ...
    def decode(self, data: bytes, schema: type | None = None) -> Any: ...

Two methods. That’s the entire contract.

Default: stdlib json

No dependencies needed:

from evoid.engines.serializer import get_serializer

serializer = get_serializer()  # auto-detects best available

# Encode
body = serializer.encode({"name": "Alice", "age": 30})

# Decode with schema
from pydantic import BaseModel

class User(BaseModel):
    name: str
    age: int

user = serializer.decode(body, schema=User)

With Pydantic

If Pydantic is installed, EVOID uses it automatically:

from pydantic import BaseModel, Field

class CreateUser(BaseModel):
    name: str = Field(min_length=1)
    email: str
    age: int = Field(ge=0, le=150)

# Decode + validate in one step
body = b'{"name": "Alice", "email": "alice@example.com", "age": 30}'
user = serializer.decode(body, schema=CreateUser)
# user is a validated Pydantic model

With msgspec

For maximum performance:

import msgspec

class User(msgspec.Struct):
    name: str
    age: int

# Fastest JSON encoding in Python
body = serializer.encode(User(name="Alice", age=30))
user = serializer.decode(body, schema=User)

Custom Serializer

Implement the protocol with your preferred library:

from evoid.engines.serializer import set_serializer

class MyCustomSerializer:
    def encode(self, data):
        # Your encoding logic
        ...

    def decode(self, data, schema=None):
        # Your decoding logic
        ...

set_serializer(MyCustomSerializer())

Auto-Detection Priority

When you call get_serializer(), EVOID picks the best available:

  1. msgspec — fastest (if installed)
  2. orjson — very fast (if installed)
  3. pydantic — with validation (if installed)
  4. stdlib json — always available (fallback)

Native IOP Style

In native IOP, serialization is a processor concern:

from evoid.native import create_service, on
from evoid import Intent, Level, Context
from evoid.engines.serializer import get_serializer

app = create_service("api")
serializer = get_serializer()

# Processor: decode request body
async def decode_body(ctx: Context) -> dict:
    raw = ctx.metadata.get("body_raw", b"")
    ctx.metadata["body"] = serializer.decode(raw)
    return {"decoded": True}

# Intent with decoder in pipeline
CREATE_USER = Intent(
    name="POST:/users",
    level=Level.STANDARD,
    metadata={
        "method": "POST",
        "path": "/users",
        "processors": ("decode_body",),
    },
)

# Handler — receives decoded data
async def handle_create_user(intent: Intent) -> dict:
    body = intent.metadata["body"]
    return {"status": "created", "name": body["name"]}

on(app, CREATE_USER, handle_create_user)

In Adapter

Adapters use the serializer to encode responses:

from evoid.engines.serializer import get_serializer

serializer = get_serializer()

async def send_response(result):
    body = serializer.encode(result.value)
    return Response(content=body, media_type="application/json")

Summary

LibraryInstallSpeedValidation
stdlib jsonbuilt-inbaselineno
pydanticpip install pydanticgoodyes
msgspecpip install msgspecfastestyes
orjsonpip install orjsonvery fastno