Usage Guide
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
[dependencies]
midiserde = "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.
Derive macros
Use #[derive(Deserialize)] and #[derive(Serialize)] with the #[mini(...)] attribute namespace:
#![allow(unused)]
fn main() {
use midiserde::{json, Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
struct Config {
name: String,
port: u16,
}
let config = Config { name: "api".into(), port: 8080 };
let j = json::to_string(&config);
let parsed: Config = json::from_str(&j)?;
}
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.
#![allow(unused)]
fn main() {
// Quick reference
#[derive(Deserialize, Serialize)]
struct ApiResponse {
#[mini(rename = "displayName")]
display_name: String,
#[mini(default)]
count: u32,
#[mini(skip_serializing_if = "Option::is_none")]
friend: Option<String>,
}
}
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:
#![allow(unused)]
fn main() {
use midiserde::{json, from_value, to_value, Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
struct Dog {
name: String,
breed: String,
}
// Two-stage: parse to Value, then deserialize to concrete type
let value: miniserde::json::Value =
json::from_str(r#"{"name": "Rex", "breed": "Golden Retriever"}"#)?;
let dog: Dog = from_value(&value)?;
// Serialize to Value for inspection or further processing
let value = to_value(&dog);
}
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) |
[dependencies]
midiserde = { version = "0.1", features = ["base64", "chrono"] }
#![allow(unused)]
fn main() {
#[derive(Deserialize, Serialize)]
struct Event {
#[mini(with = "midiserde::with::rfc3339")]
created_at: chrono::DateTime<chrono::Utc>,
}
#[derive(Deserialize, Serialize)]
struct Payload {
#[mini(with = "midiserde::with::base64")]
data: Vec<u8>,
}
#[derive(Deserialize, Serialize)]
struct Config {
#[mini(with = "midiserde::with::time_delta")]
timeout: chrono::TimeDelta, // JSON: [30, 0] for 30 seconds
}
}
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