Skip to content
Development
Skill

/msgspec

Auto-activate for msgspec, Struct, Meta, msgspec.json, msgspec.msgpack, tagged unions, enc_hook, dec_hook, convert(), or Litestar DTO shapes. Not for Pydantic or ORM models — use their stack-specific skill.

From plugin
litestar
1431 skills1 agent1 hook
Install
$ npx -y skills add litestar-org/litestar-skills --skill msgspec --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/msgspec

Context preview

The summary Claude sees to decide when to auto-load this skill.

Auto-activate for msgspec, Struct, Meta, msgspec.json, msgspec.msgpack, tagged unions, enc_hook, dec_hook, convert(), or Litestar DTO shapes. Not for Pydantic or ORM models — use their stack-specific skill.

SKILL.md

msgspec.SKILL.md
name: msgspec
description: "Auto-activate for msgspec, Struct, Meta, msgspec.json, msgspec.msgpack, tagged unions, enc_hook, dec_hook, convert(), or Litestar DTO shapes. Not for Pydantic or ORM models — use their stack-specific skill."

msgspec Skill

msgspec is a high-performance Python library for serialization, deserialization, and typed validation. This guidance targets the immutable `0.21.1` release.

Code Style Rules

  • Use PEP 604 for unions: `T | None` (not `Optional[T]`)
  • **`from __future__ import annotations` rule** — Library/shared modules that define runtime-introspected `msgspec.Struct` subclasses should avoid postponed annotations unless the consuming tool resolves them. Consumer modules that only use Structs MAY use future annotations.
  • Annotate every serialized field; only annotated attributes become Struct fields
  • Use `kw_only=True` for Structs with more than 2 fields
  • Put wire-name configuration on `msgspec.field(name=...)` or the Struct's `rename=`

option; `msgspec.Meta` defines constraints and JSON Schema metadata, not field aliases

Quick Reference

Struct Definition

import msgspec


# Basic struct
class User(msgspec.Struct):
    id: int
    name: str
    email: str | None = None


# Performance options
class Event(msgspec.Struct, frozen=True, gc=False):
    """frozen=True: immutable + hashable. gc=False: skip GC for short-lived objects."""

    event_type: str
    payload: dict[str, object]


# Keyword-only (recommended for >2 fields)
class Config(msgspec.Struct, kw_only=True):
    host: str
    port: int = 5432
    ssl: bool = False


# Array-like encoding (tuple encoding, more compact)
class Point(msgspec.Struct, array_like=True):
    x: float
    y: float


# Rename fields for serialization
class ApiResponse(msgspec.Struct, rename="camel"):
    user_id: int  # serialized as "userId"
    created_at: str  # serialized as "createdAt"


# Rename one field explicitly
class Resource(msgspec.Struct):
    resource_id: int = msgspec.field(name="id")


# Reject unknown fields at API boundaries
class StrictInput(msgspec.Struct, forbid_unknown_fields=True):
    name: str
    value: int

Validation Constraints

from datetime import datetime
from typing import Annotated

import msgspec
from msgspec import Meta


class Product(msgspec.Struct):
    name: Annotated[str, Meta(min_length=1, max_length=100)]
    price: Annotated[float, Meta(gt=0)]
    quantity: Annotated[int, Meta(ge=0, le=10_000)]
    sku: Annotated[str, Meta(pattern=r"^[A-Z]{2}-\d{4}$")]
    batch_size: Annotated[int, Meta(multiple_of=5)]
    expires_at: Annotated[datetime, Meta(tz=True)]


# Reusable constraint aliases
PositiveInt = Annotated[int, Meta(gt=0)]
NonEmptyStr = Annotated[str, Meta(min_length=1)]
Percentage = Annotated[float, Meta(ge=0.0, le=100.0)]


class Order(msgspec.Struct):
    id: PositiveInt
    label: NonEmptyStr
    discount: Percentage = 0.0

Serialization

import msgspec

# JSON -- singleton encoder/decoder (cache these!)
encoder = msgspec.json.Encoder()
decoder = msgspec.json.Decoder(User)

data = encoder.encode(user)  # bytes
user = decoder.decode(b'{"id":1,"name":"Alice"}')

# Functional API (convenience, slightly slower)
data = msgspec.json.encode(user)
user = msgspec.json.decode(b"...", type=User)

# MessagePack (binary, more compact)
data = msgspec.msgpack.encode(user)
user = msgspec.msgpack.decode(data, type=User)

# Hooks are only for unsupported custom types. datetime, UUID, Decimal, and
# Enum are already supported.


def enc_hook(obj: object) -> object:
    if isinstance(obj, complex):
        return (obj.real, obj.imag)
    raise NotImplementedError(f"Unsupported type: {type(obj)}")


def dec_hook(target_type: type, obj: object) -> object:
    if target_type is complex:
        real, imag = obj
        return complex(real, imag)
    raise NotImplementedError(f"Unsupported type: {target_type}")


encoder = msgspec.json.Encoder(enc_hook=enc_hook)
decoder = msgspec.json.Decoder(MyStruct, dec_hook=dec_hook)

`dec_hook` runs only for unsupported custom annotations. `TypeError` and `ValueError` raised by the hook become path-aware `ValidationError`s. In 0.21.1, a `ValidationError` or `DecodeError` raised by the hook propagates directly and is not wrapped in another `ValidationError`.

Canonical Litestar serializers (match-your-stack)

Litestar apps typically need `to_json(value, as_bytes=True)` that handles UUID / datetime / Enum / Decimal for Channels broadcasts, log contexts, and JSONB writes. Pick the branch that matches your project.

**Branch A — sqlspec is in-stack.** Re-export sqlspec's serializer; it already installs an `enc_hook` covering UUID, datetime, Enum, Decimal, Pydantic, dataclasses, attrs, and msgspec.Struct.

# myapp/utils/serialization.py
from sqlspec.utils.serializers import from_json, to_json

__all__ = ("from_json", "to_json")

Usage:

from myapp.utils.serialization import to_json

payload = to_json(order, as_bytes=True)
await backend.publish(payload, channels=[f"orders:{order.id}:events"])

**Branch B — sqlspec is not in-stack.** Use a plain msgspec `Encoder`; the package natively handles UUID, datetime, date, time, Decimal, Enum, dataclasses, attrs classes, and Structs.

# myapp/utils/serialization.py
from typing import Any

import msgspec


_encoder = msgspec.json.Encoder()


def to_json(value: Any) -> bytes:
    if isinstance(value, bytes):
        return value
    return _encoder.encode(value)

Type Coercion with convert()

import msgspec

raw = {"id": "42", "name": "Alice"}  # id is a string

# Strict mode (default): raises on type mismatch
user = msgspec.convert(raw, User)  # ValidationError: id must be int

# Lax mode: coerces compatible types
user = msgspec.convert(raw, User, strict=False)  # id coerced to 42

# str_keys: dict keys are strings (useful for JSON-loaded dicts)
data = {"1": "Alice", "2": "Bob"}
result = ms
Read more
Ships withlitestar

Opinionated, first-party agent skills, plugins, subagents, slash commands, and MCP servers for the Litestar framework and its ecosystem — publishable to every major AI agent and IDE from a single repo.

Get the whole plugin

Other skills on litestar.