midiserde 0.1.1

When mini isn't enough and serde is too much
Documentation
# 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). Multiple struct flattens are supported with no ordering constraints.
2. **Map flatten**: Use `HashMap<String, Value>` or `BTreeMap<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.

```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:

| 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) |

```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

| 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