# midiserde
**When serde is too much and miniserde is not enough.**
A set of utilities that enhance [miniserde](https://github.com/dtolnay/miniserde) to provide more of the serde functionality—without the bloat.
## Why midiserde?
[miniserde](https://github.com/dtolnay/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](https://serde.rs/) 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
```toml
[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`.
## Usage
### Derive macros
Use `#[derive(Deserialize)]` and `#[derive(Serialize)]` with the `#[mini(...)]` attribute namespace:
```rust
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:
1. **Struct flatten**: Inline a nested struct’s fields into the parent JSON object (e.g. pagination metadata at the top level).
2. **Map flatten**: Use `HashMap<String, Value>` or `BTreeMap<String, Value>` to capture unknown JSON keys.
When both struct flatten and map flatten appear, the struct flatten must list its field names: `#[mini(flatten = "limit, offset, total")]` so the map can consume the remaining keys. 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.
```rust
// 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`:
```rust
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:
| `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) |
```toml
[dependencies]
midiserde = { version = "0.1", features = ["base64", "chrono"] }
```
```rust
#[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
| `macros` | ✓ | 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