Derives: Serialization (Reference)¶
This page documents Serialize and Deserialize for JSON.
See also:
Module derive¶
Import std.serde's json module when a type should adopt both JSON traits through one derive:
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()
@derive(json) adopts json.Serialize and json.Deserialize, forwards the required Rust serde derives, and makes the adopted traits available to method lookup and generic bounds. Import from std.serde.json directly when you want only one side of the protocol.
Serialize¶
- Derive:
@derive(json)for both JSON directions, or@derive(Serialize)after importingSerializedirectly - API:
json_stringify(value) -> str - Trait import:
from std.serde.json import Serialize+with Serializegives a default.to_json()implementation
from std.serde.json import Serialize
@derive(Serialize)
model User:
name: str
age: int
def main() -> None:
u = User(name="Alice", age=30)
println(json_stringify(u))
from std.serde.json import Serialize
model User with Serialize:
name: str
age: int
def main() -> None:
println(User(name="Alice", age=30).to_json())
Deserialize¶
- Derive:
@derive(json)for both JSON directions, or@derive(Deserialize)after importingDeserializedirectly - API:
T.from_json(input: str) -> Result[T, str] - Trait import:
with Deserializestill requires either an imported@derive(Deserialize)or an explicitfrom_json()implementation
from std.serde.json import Deserialize
@derive(Deserialize)
model User:
name: str
age: int
def main() -> None:
result: Result[User, str] = User.from_json("{\"name\":\"Alice\",\"age\":30}")
Schema-safe field names (models only)¶
If your JSON schema uses keys that are not valid Incan identifiers (or are keywords like type), represent them using a model field alias and choose a schema-safe canonical field name (e.g. type_).
from std.serde import json
@derive(json)
model Account:
type_ as "type": str
When json is derived, the alias is used as the JSON key ("type"). The canonical identifier (type_) remains the stable field name in code. See Models: Using aliases in code.
class does not support field metadata/aliases, so class JSON keys always match the canonical field names.
Enums¶
Ordinary enums support @derive(json) just like models:
from std.serde import json
@derive(json)
enum Status:
Pending
Active
Completed
@derive(json)
enum ApiResponse:
Success(str)
Error(int, str)
Value enums serialize and deserialize through their declared raw value rather than the variant name:
from std.serde import json
@derive(json)
enum Environment(str):
Development = "development"
Production = "production"
@derive(json)
enum HttpStatus(int):
Ok = 200
NotFound = 404
Environment.Production serializes as "production" and deserializes only from known raw values. HttpStatus.NotFound serializes as 404.
When a model references an enum in its fields, the compiler automatically propagates JSON serde derives to the enum:
from std.serde import json
@derive(json)
enum Priority:
Low
Medium
High
@derive(json)
model Task:
name: str
priority: Priority # Priority automatically gets serde derives
Newtypes¶
Newtypes also support @derive(json):
from std.serde import json
@derive(json)
newtype UserId(int)
@derive(json)
newtype Email(str)
Newtypes serialize to/from their underlying type's JSON representation.
Dynamic JSON fields¶
Use std.json.JsonValue when one model field should remain dynamic while the rest of the model is typed:
from std.serde import json
from std.json import JsonValue
@derive(json)
model ApiResponse:
status: int
data: JsonValue
JsonValue fields serialize and deserialize as their contained JSON value. They do not add a wrapper object around the field.
Type mappings (Incan → JSON)¶
| Incan | JSON |
|---|---|
str |
string |
int |
number |
float |
number |
bool |
boolean |
List[T] |
array |
Dict[str, T] |
object |
Option[T] |
value or null |
JsonValue |
dynamic JSON value |
model / class |
object |
ordinary enum |
variant encoding |
value enum |
backing str / int |
newtype |
underlying type |