---
title: "Deser: Rethinking Rust Serialization"
slug: deser-rethinking-rust-serialization
url: https://listedarticles.com/articles/deser-rethinking-rust-serialization
canonical_url: https://lucumr.pocoo.org/2026/9/29/deser/
content_type: blog_post
language: en
published_at: 2026-09-29T21:00:00.000Z
updated_at: 2026-09-30T00:16:14.489Z
author: "Armin Ronacher"
author_url: https://lucumr.pocoo.org/
authored_by: human
publisher: "Armin Ronacher"
publisher_url: https://lucumr.pocoo.org/
topics: ["Rust", "Programming", "Open Source", "Systems Programming", "Performance"]
license: all-rights-reserved
word_count: 492
reading_minutes: 2
citation: "Armin Ronacher, Armin Ronacher. \"Deser: Rethinking Rust Serialization.\" 29 Sept 2026. https://lucumr.pocoo.org/2026/9/29/deser/ (all-rights-reserved)"
# The full text follows. The web page shows an extract and sends readers
# to the source above; quote the citation and link the canonical URL.
---

# Deser: Rethinking Rust Serialization

> Armin Ronacher revisits Deser, an experimental Rust serialization library that inverts Serde’s visitor recursion into heap-backed sinks/emitters—trading some performance for lossless buffering, composable adapters, XML namespaces, and no stack overflow on deep nests.

# Deser: Rethinking Rust Serialization

written on September 29, 2026

Serde is an amazing serialization library for Rust. However already while at Sentry I got quite frustrated with some of the limitations. Replacing Serde is tricky because of the might that it has in the ecosystem—and because it's quite hard to actually do better without painful compromises.

Three examples of Serde corner cases:

1. **A number that is a map** — with `serde_json`'s `arbitrary_precision` feature, an internally tagged enum can fail with `invalid type: map, expected f64` because the buffer doesn't know about the magic key used for in-band signalling.
2. **Flattening breaks integer keys** — `HashMap<u32, u32>` parses alone but fails under `#[serde(flatten)]` because buffered keys stay strings.
3. **Adapters do not compose** — a `deserialize_with` function cannot be applied inside `Option`/`Vec` without writing another wrapper function for every container.

None of these are easy to fix in Serde; they fall out of its design and stability guarantees.

## The Name And Idea

The name is Serde with its two halves swapped. In Serde, a type drives deserialization: a `Deserialize` impl asks for the kind of value it expects; nested values recurse on the stack. Deser turns this around: the format tells the type of the next value and pushes events into a sink. Nested sinks are handed back to a driver that keeps state on the heap (in an arena). Emitters return nested values instead of recursing.

That means Deser cannot support non-self-describing formats like protobuf—intentionally left out.

Most of the reasons go back to Sentry Relay processing enormous amounts of untrusted JSON. Problems come from three Serde decisions: one set of traits for all formats; a fixed data model that loses information when buffering; and recursion on the call stack.

## Deser's Design

Surface UX stays familiar—derive `Serialize`/`Deserialize`—but the architecture yields:

- No stack overflows on arbitrary nesting
- Suspendable / `Send` deserializations for streaming and async
- An extensible data model with extension values (DateTime, Uuid, …) instead of magic maps
- Lossless buffering that preserves format knowledge and error locations
- Middleware layers (paths, limits, renaming, redaction)
- Native flattening that doesn't buffer
- Composable adapters (`as = Option<Vec<Hex>>`), validation as adapters, expression-valued attributes

XML namespaces, interleaved repeated elements, and format-native datetimes illustrate where the design pays off versus Serde-based crates.

## The Cost

Dynamic dispatch and heap-backed sinks have runtime overhead. For JSON, reads are on average about 10% slower than `serde_json` (wide variance). Compile times for derived code can be better (~2.3× faster release builds of derived code in Ronacher's measurements). The crate uses `unsafe` internally for borrowed sink chains. And the biggest cost: it's just not Serde.

## How Much Is There?

Beyond the core and derive macros: JSON/JSONC/JSON5/HJSON, CBOR, MessagePack, YAML 1.1/1.2, TOML, XML, Apple plists, CSV/TSV, urlencoded, env vars, path/location layers, validation, binary encodings, a Serde bridge, dynamic values, and tokio hooks.

*Full post with code samples: [lucumr.pocoo.org/2026/9/29/deser](https://lucumr.pocoo.org/2026/9/29/deser/).*
