Skip to content

Derives: Serialization (reference)

This page specifies JSON serialization: the json derive, Serialize and Deserialize, field names, and how each type maps to JSON. The derive catalog is in Derives and traits.

Spellings

Spelling Import Provides
@derive(json) from std.serde import json Serialize and Deserialize
@derive(Serialize), or with Serialize from std.serde.json import Serialize Serialize
@derive(Deserialize), or with Deserialize from std.serde.json import Deserialize Deserialize

After from std.serde import json, the traits are also named json.Serialize and json.Deserialize. Each spelling applies to a model, class, enum or newtype.

Serialize

  • Provides: value.to_json() -> str, json_stringify(value) -> str, and T with Serialize bounds.
  • Provided by: @derive(json), @derive(Serialize), or adopting Serialize.
  • Behavior: the value is written as JSON by the type mapping.
  • Requires: every field type serializes. A field, payload or underlying type that holds a model or class that does not provide Serialize, or a type with no JSON form (below), is refused (INCAN-T0001).

Deserialize

  • Provides: T.from_json(json_str: str) -> Result[T, str], and T with Deserialize bounds.
  • Provided by: @derive(json) or @derive(Deserialize); adopting Deserialize and defining from_json.
  • Behavior: the input is read by the type mapping. Input that does not match returns Err.
  • Requires: every field type deserializes. A field, payload or underlying type that holds a model or class that does not provide Deserialize, or a type with no JSON form (below), is refused (INCAN-T0001). A type that adopts Deserialize without deriving it defines from_json, or it is refused (INCAN-T0001).
from std.serde import json

@derive(json)
model User:
    name: str
    age: int

def encode[T with json.Serialize](value: T) -> str:
    return value.to_json()

def main() -> None:
    text = encode(User(name="Alice", age=30))               # accepted
    parsed: Result[User, str] = User.from_json(text)        # accepted

Bounds and trait types

Position Accepts Provides
T with Serialize, for value: T a type argument that provides Serialize value.to_json() -> str, json_stringify(value) -> str
T with Deserialize a type argument that provides Deserialize T.from_json(json_str: str) -> Result[T, str]
a parameter value: Serialize an argument whose type provides Serialize value.to_json() -> str, json_stringify(value) -> str
a return type -> Serialize a returned value whose type provides Serialize to_json() and json_stringify over the result
  • A type argument, argument or returned value whose type does not provide the trait is refused (INCAN-T0001). A builtin type such as int does not provide Serialize.
  • json_stringify(value) writes a value of any type in the type mapping. A model or class value that does not provide Serialize, or a value of a type parameter or trait whose bounds do not provide it, is refused, alone or inside a collection, Option or tuple (INCAN-T0001).
  • Serialize or Deserialize written inside another type in a signature or a local annotation, such as -> Option[Serialize] or items: list[Serialize], is refused (INCAN-T0001). items: list[T] for a type parameter T with Serialize is accepted.
  • In a model or class whose type parameter is bounded T with Serialize, a field of type T, or of a collection of T, serializes.
from std.serde.json import Deserialize, Serialize

@derive(Serialize, Deserialize)
model Payload:
    value: int

@derive(Serialize, Deserialize)
type UserId = newtype int

@derive(Serialize)
model Envelope[T with Serialize]:
    payload: T

def encode[T with Serialize](value: T) -> str:
    return value.to_json()

def stringify[T with Serialize](value: T) -> str:
    return json_stringify(value)

def decode[T with Deserialize](text: str) -> Result[T, str]:
    return T.from_json(text)

def describe(value: Serialize) -> str:
    return json_stringify(value)

def make() -> Serialize:
    return Payload(value=5)

def main() -> None:
    a = encode(Payload(value=1))                        # accepted
    b = stringify(UserId(8))                            # accepted
    c = decode[UserId]("9")                             # accepted
    d = describe(Payload(value=6))                      # accepted
    e = make().to_json()                                # accepted
    f = Envelope(payload=Payload(value=3)).to_json()    # accepted
    g = encode(1)                                       # refused: int does not provide Serialize (INCAN-T0001)

Field names

  • A model field's JSON key is its alias when it declares one (type_ as "type": str, or type_ [alias="type"]: str), and its name otherwise.
  • A class field's JSON key is its name. An alias on a class field is refused (INCAN-T0001).
  • A name is the key as written, including a name such as impl or dyn (see Identifier): Slot(impl=1) serializes as {"impl":1}.

Type mapping

Type JSON
str string
int, the exact-width integers, float, f32, f64 number
bool true or false
bytes array of its byte values, each a number from 0 to 255: b"hi" writes [104,105]
list[T], set[T], a tuple array
dict[str, T] object
dict[K, T] for any other K object whose keys are the keys' JSON forms written as strings: an int key 1 writes "1", a bool key writes "true" or "false", and a key whose JSON form is a string writes that string
Option[T] the value, or null for None
Result[T, E] {"Ok": value} or {"Err": error}
std.json.JsonValue the JSON value it holds
model, class object with one key per field
enum that declares values the variant's value: enum Environment(str) with Production = "production" writes "production", and enum HttpStatus(int) with NotFound = 404 writes 404
Any other enum a variant without a payload as its name ("Pending"); a variant with one payload as an object from its name to the payload ({"Success": "ok"}); a variant with several payloads as an object from its name to an array of them ({"Error": [404, "missing"]})
newtype its underlying value
  • decimal, FrozenStr, FrozenBytes, FrozenList, FrozenSet and FrozenDict have no JSON form. A field, payload or underlying type that holds one, directly or inside another type, is refused (INCAN-T0001), and so is a route payload that holds one (INCAN-T0112).
  • A dict key whose JSON form is not a string, a number or a bool, such as a model, a tuple or an enum variant with a payload, raises TypeError when the value is serialized.
  • A newtype with a from_underlying hook or constraints deserializes through them: a value they refuse returns Err.
  • An enum or newtype that a serializing model or class names as a field type serializes the same way, without its own derive.

See also