midiserde
When serde is too much and miniserde is not enough.
A set of utilities that enhance miniserde to provide more of the serde functionality—without the bloat.
Why midiserde?
miniserde gives you JSON serialization with a tiny dependency footprint and no proc-macro-heavy codegen. But it stops short of the convenience many projects need: derive macros, field attributes, value conversion, and common type adapters.
serde gives you everything—but at a cost. Its trait-based design and extensive monomorphization can blow up binary size and compile times, especially when you have hundreds or thousands of serializable types.
midiserde fills the gap: miniserde's lean core, plus the ergonomics you expect.
Use cases
- Embedded and WebAssembly — Strip serde monomorphization bloat, slash executable size, and cut compile times. Miniserde's simpler trait design generates far less monomorphized code than serde's.
- Large API bindings — SDKs and clients with thousands of data types can reduce bloat by many megabytes by switching to miniserde + midiserde instead of serde.
- Lightweight services — Fast builds and smaller binaries without sacrificing attribute-based customization.
Installation
[]
= "0.1"
The macros feature is enabled by default, providing Deserialize and Serialize derive macros.
For runnable examples, run cargo run -p midiserde_examples --example <name> with: rename, defaults, flatten, skip, skip_serializing_if, custom_with, base64, chrono, two_stage.
Usage
Derive macros
Use #[derive(Deserialize)] and #[derive(Serialize)] with the #[mini(...)] attribute namespace:
use ;
let config = Config ;
let j = to_string;
let parsed: Config = from_str?;
Customization options
All attributes use the #[mini(...)] namespace. See midiserde_examples for full examples of each.
#[mini(rename = "json_key")] — Map a different JSON key to this field (or enum variant). Useful for camelCase APIs (displayName) or API naming conventions.
#[mini(default)] — When the field is missing during deserialization, use Default::default(). Requires T: Default.
#[mini(default = "path::to::func")] — When the field is missing, call a custom function fn() -> T instead of Default::default(). The function path must be a valid Rust path (e.g. default_timeout_ms or my_module::defaults::retries). No T: Default bound is required.
#[mini(with = "module::path")] — Custom serialization/deserialization. The module must provide begin(out: &mut Option<T>) -> &mut dyn Visitor and serialize(value: &T) -> Fragment. See the built-in with modules or custom_with example.
#[mini(flatten)] — Two modes:
- Struct flatten: Inline a nested struct's fields into the parent JSON object (e.g. pagination metadata at the top level). Multiple struct flattens are supported with no ordering constraints.
- Map flatten: Use
HashMap<String, Value>orBTreeMap<String, Value>to capture unknown JSON keys (at most one per struct).
Key routing is automatic: each struct knows its own fields via FlattenMap::accepts_key, so no field lists are needed — even when mixing multiple struct flattens with a map flatten. All flatten serialization is zero-copy streaming via SerializeMapBuilder (no intermediate Value): struct flattens use the derived impl, and HashMap/BTreeMap flattens use midiserde's built-in impls. See midiserde_examples -> flatten for the full pattern.
#[mini(skip)] — Skip both serialization and deserialization. The field always uses Default::default() on deserialize.
#[mini(skip_serializing)] — Omit the field when serializing; still deserialize it if present in JSON.
#[mini(skip_deserializing)] — Never read from JSON; always Default::default(). The field is still serialized.
#[mini(skip_serializing_if = "path")] — Call a fn(&T) -> bool; omit the field when serializing if it returns true. Common: Option::is_none, Vec::is_empty, or a custom predicate.
// Quick reference
to_value and from_value
Miniserde doesn't ship from_value/to_value. Midiserde does — the miniserde equivalent of serde_json::from_value and serde_json::to_value:
use ;
// Two-stage: parse to Value, then deserialize to concrete type
let value: Value = from_str?;
let dog: Dog = from_value?;
// Serialize to Value for inspection or further processing
let value = to_value;
Useful for extra-field capture, polymorphic buffers, and conditional deserialization.
Built-in with modules
Ready-made adapters for common types. Enable via features:
| Module | Feature | Type | JSON representation |
|---|---|---|---|
midiserde::with::base64 |
base64 |
Vec<u8> |
Base64 string |
midiserde::with::rfc3339 |
chrono |
chrono::DateTime<Utc> |
RFC 3339 string, e.g. "2024-02-21T14:30:00Z" |
midiserde::with::timestamp |
chrono |
chrono::DateTime<Utc> |
Unix timestamp (seconds) |
midiserde::with::time_delta |
chrono |
chrono::TimeDelta |
[seconds, nanoseconds] array (chrono-compatible) |
[]
= { = "0.1", = ["base64", "chrono"] }
Features
| Feature | Default | Description |
|---|---|---|
macros |
yes | Enable Deserialize and Serialize derive macros |
base64 |
- | midiserde::with::base64 for Vec<u8> |
chrono |
- | rfc3339, timestamp, time_delta for chrono types |
full |
- | All optional features (handy for tests) |
License
Apache-2.0