Expand description
§serde-shape
serde-shape reflects the data model that a Rust type emits through Serde serialization or
accepts through Serde deserialization. It builds a lightweight graph from type information and
#[serde(...)] attributes without serializing or deserializing a value.
Common uses are generating configuration reference docs, deriving environment-variable maps from config structs, documenting wire formats, and checking whether two versions of a type expose compatible Serde shapes.
§Getting started
Enable the derive feature when you want #[derive(SerializeShape)] and
#[derive(DeserializeShape)]:
[dependencies]
serde-shape = { version = "0.1.0", features = ["derive"] }Enable std when the reflected types use shapes provided only by the Rust standard library:
[dependencies]
serde-shape = { version = "0.1.0", features = ["derive", "std"] }The crate is no_std by default and requires alloc.
The shape derives are independent of Serde’s Serialize and Deserialize derives: they
neither implement nor require those traits. Derive both sets when a type must also perform
actual serialization or deserialization. serde_shape attributes affect reflection metadata
only; they do not change Serde’s runtime behavior.
§Inspecting a graph
Derive DeserializeShape for the type you want to inspect, then build a
DeserializeShapeGraph:
use serde_shape::DeserializeDefinitionKind;
use serde_shape::DeserializeShape;
use serde_shape::FieldsStyle;
#[derive(DeserializeShape)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
struct Config {
http_port: u16,
peers: Vec<String>,
tls: Option<TlsConfig>,
}
#[derive(DeserializeShape)]
#[serde(rename_all = "kebab-case")]
struct TlsConfig {
cert_path: String,
key_path: String,
}
let graph = Config::deserialize_shape();
let config = graph.root_definition().unwrap();
let DeserializeDefinitionKind::Struct(shape) = &config.kind else {
panic!("Config should produce a struct shape");
};
assert_eq!(config.type_name.name, "Config");
assert_eq!(shape.style, FieldsStyle::Struct);
assert!(shape.attributes.deny_unknown_fields);
assert_eq!(shape.fields[0].name, "http-port");
assert_eq!(shape.fields[1].name, "peers");
assert_eq!(shape.fields[2].name, "tls");Serialization and deserialization are reflected separately because Serde lets the two directions differ:
use serde_shape::DeserializeDefinitionKind;
use serde_shape::DeserializeShape;
use serde_shape::SerializeDefinitionKind;
use serde_shape::SerializeShape;
#[derive(SerializeShape, DeserializeShape)]
#[serde(rename(serialize = "wire-output", deserialize = "wire-input"))]
struct Message {
#[serde(rename(serialize = "out-id", deserialize = "in-id"))]
id: u64,
}
let serialize_graph = Message::serialize_shape();
let deserialize_graph = Message::deserialize_shape();
let serialize_definition = serialize_graph.root_definition().unwrap();
let deserialize_definition = deserialize_graph.root_definition().unwrap();
assert_eq!(serialize_definition.type_name.name, "wire-output");
assert_eq!(deserialize_definition.type_name.name, "wire-input");
let SerializeDefinitionKind::Struct(serialize_shape) = &serialize_definition.kind else {
panic!("Message should produce a struct serialization shape");
};
let DeserializeDefinitionKind::Struct(deserialize_shape) = &deserialize_definition.kind else {
panic!("Message should produce a struct deserialization shape");
};
assert_eq!(serialize_shape.fields[0].name, "out-id");
assert_eq!(deserialize_shape.fields[0].name, "in-id");§Shape graphs
A shape graph has a ShapeRef root and a list of named definitions. Flat primitive and
compound values are represented directly as ShapeRef values. Structs and enums are
stored as named definitions and referenced by ShapeId.
Definition IDs are local to one graph. They contain an index but no graph identity, so callers
must keep each ID paired with the graph that produced it. Use
SerializeShapeGraph::definition or DeserializeShapeGraph::definition to resolve them.
Definition ordering and debug output are not stable persistence formats.
Definitions may be recursive, so graph walkers must detect repeated ShapeId values before
following definition references.
Types that branch on Serde’s human-readable mode may expose a union of their known representations. Shape graphs describe possible semantic shapes across formats rather than specializing themselves for one serializer.
ShapeRef is not a trace of exact serializer or deserializer method calls. It deliberately
preserves useful Rust distinctions such as fixed arrays and pointer-width integers even when
Serde dispatches them through tuple or fixed-width integer methods.
§Derive behavior
The derive macros use Serde’s derive metadata for the selected direction. They reflect directional names and skips, rename rules, aliases, defaults, enum tagging, flattening, transparent fields, identifier enums, conversion types, remote definitions, and custom serializer or deserializer boundaries.
A custom serializer or deserializer has no inferable inner shape, so the affected field or
variant content is represented by an opaque boundary. Whole-container conversion attributes
use the conversion type’s shape. Serde remote derives expose the helper definition’s declared
wire shape.
Field-level FieldWireShape distinguishes ordinary values from flattened fields, inline
transparent fields, and omitted fields. Custom serializer/deserializer boundaries use
ShapeRef::Opaque and remain composable with those field positions.
Use #[serde_shape(serialize_with = "path")] or
#[serde_shape(deserialize_with = "path")] to declare the representation of a container,
variant, or field that cannot be inferred. Each function receives the current graph context and
returns a ShapeRef, so it can delegate to another type or build a custom shape directly.
These extensions supply reflection metadata only: they cannot rename, tag, skip, flatten,
alias, or default a Serde item.
Rust doc comments on derived containers, variants, and fields are preserved in their
description fields for documentation and diagnostic consumers.
§Manual implementations
Implement SerializeShape or DeserializeShape manually when a type’s Serde
representation is known but cannot be derived. This is common for wrappers that deserialize
from a string or another primitive representation:
use serde_shape::DeserializeShape;
use serde_shape::DeserializeShapeContext;
use serde_shape::ShapeRef;
struct ByteSize(u64);
impl DeserializeShape for ByteSize {
fn deserialize_shape_in(_context: &mut DeserializeShapeContext) -> ShapeRef {
ShapeRef::union([ShapeRef::String, ShapeRef::U64])
}
}
assert_eq!(
ByteSize::deserialize_shape().root(),
&ShapeRef::union([ShapeRef::String, ShapeRef::U64])
);For recursive or shared named types, use SerializeShapeContext::define_named_type or
DeserializeShapeContext::define_named_type so the graph contains one definition and
all recursive edges point back to it.
Structs§
- Deserialize
Container Attributes - Container metadata relevant to deserialization.
- Deserialize
Definition Shape - A named type definition in a deserialization shape graph.
- Deserialize
Enum Shape - Enum-like deserialization metadata.
- Deserialize
Field Shape - Field-level deserialization metadata.
- Deserialize
Shape Context - A context that accumulates named deserialization definitions while a graph is built.
- Deserialize
Shape Graph - A complete deserialization shape graph rooted at one type.
- Deserialize
Struct Shape - Struct-like deserialization metadata.
- Deserialize
Variant Shape - Variant-level deserialization metadata.
- Opaque
Shape - An intentionally opaque shape.
- Serialize
Container Attributes - Container metadata relevant to serialization.
- Serialize
Definition Shape - A named type definition in a serialization shape graph.
- Serialize
Enum Shape - Enum-like serialization metadata.
- Serialize
Field Shape - Field-level serialization metadata.
- Serialize
Shape Context - A context that accumulates named serialization definitions while a graph is built.
- Serialize
Shape Graph - A complete serialization shape graph rooted at one type.
- Serialize
Struct Shape - Struct-like serialization metadata.
- Serialize
Variant Shape - Variant-level serialization metadata.
- ShapeId
- A graph-local identifier for a named shape definition.
- Type
Name - Names associated with a Rust type and one direction of its Serde representation.
- Union
Shape - The normalized alternatives contained by
ShapeRef::Union.
Enums§
- Default
Shape - A Serde default marker.
- Deserialize
Definition Kind - The body of a named deserialization definition.
- Deserialize
Variant Content - The deserialized content controlled by an enum variant.
- Field
Member - The Rust member represented by a field.
- Field
Wire Shape - How a field contributes to the wire representation in one Serde direction.
- Fields
Style - The style of a struct, variant, or tuple field list.
- Opaque
Reason - The reason a shape cannot be represented precisely.
- Serialize
Definition Kind - The body of a named serialization definition.
- Serialize
Variant Content - The serialized content controlled by an enum variant.
- Shape
Ref - A reference to a shape node.
- Tagging
- Serde container or enum tagging representation.
Traits§
- Deserialize
Shape - A type that can describe the shape accepted by its Serde deserializer.
- Serialize
Shape - A type that can describe the shape emitted by its Serde serializer.
Derive Macros§
- Deserialize
Shape derive - Derives
DeserializeShapefrom Serde deserialization metadata. - Serialize
Shape derive - Derives
SerializeShapefrom Serde serialization metadata.