{
  "@context": "https://schema.org",
  "@id": "https://aggr.aymericbeaumet.com/items/lucumr-pocoo-org/2026-09-29-deser-rethinking-rust-serialization/#webpage",
  "@type": [
    "WebPage",
    "ArchiveComponent"
  ],
  "articleBody": "[Serde](https://serde.rs/) is an amazing serialization library for Rust and it has been a huge reason why I felt productive with it for years. However already while at Sentry I got quite frustrated with some of the limitations with it but actually replacing Serde is tricky because of the might that it has in the ecosystem. Also because it’s quite hard to actually do better without also making some potentially painful compromises.\n\nHere are three examples of Serde corner cases that show poor interactions of Serde features or unexpected limitations:\n\nA number that is a map\n\nAn internally tagged enum, with `serde_json`‘s `arbitrary_precision` feature turned on:\n\n```\n#[derive(Deserialize)]\n#[serde(tag = \"type\")]\nenum Shape {\n    Circle { radius: f64 },\n}\n\nserde_json::from_str::<Shape>(r#\"{\"type\": \"Circle\", \"radius\": 1.5}\"#)\n// error: invalid type: map, expected f64\n```\n\nSerde’s data model has no place for arbitrary precision numbers, so `serde_json` uses in-band signalling with a map with a magic key. The enum has to buffer the fields until it has seen the tag, and the buffer does not know about the magic key. Because Cargo features are unified, it’s enough for any crate in your dependency graph to turn the feature on.\n\nFlattening breaks integer keys\n\n```\n#[derive(Deserialize)]\nstruct Stats {\n    scores: HashMap<u32, u32>,\n}\n\n#[derive(Deserialize)]\nstruct Report {\n    name: String,\n    #[serde(flatten)]\n    stats: Stats,\n}\n\nserde_json::from_str::<Report>(r#\"{\"name\": \"x\", \"scores\": {\"42\": 23}}\"#)\n// error: invalid type: string \"42\", expected u32 at line 1 column 35\n```\n\n`Stats` on its own parses `{\"scores\": {\"42\": 23}}` just fine. JSON keys are always strings, and `serde_json` only turns them into integers if the type asks for one. However once `flatten` buffers the value, `\"42\"` is just a string. The error also points at the end of the document rather than at the key.\n\nAdapters do not compose\n\n```\nfn from_hex<'de, D: Deserializer<'de>>(d: D) -> Result<u32, D::Error> { ... }\n\n#[derive(Deserialize)]\nstruct Theme {\n    #[serde(deserialize_with = \"from_hex\")]\n    primary: u32,\n    #[serde(deserialize_with = \"from_hex\")]\n    accent: Option<u32>,\n}\n\n//error[E0308]: `?` operator has incompatible types\n//  |\n//  |     #[serde(deserialize_with = \"from_hex\")]\n//  |                                ^^^^^^^^^^ expected `Option<u32>`, found `u32`\n//  |\n//help: try wrapping the expression in `Some`\n//  |\n//  |     #[serde(deserialize_with = Some(\"from_hex\"))]\n//  |                                +++++          +\n```\n\nA function cannot be passed as a type parameter, so there is no way to apply `from_hex` to the inside of an `Option`, a `Vec` or a map. You write another function for every wrapper, and once you have `from_opt_hex` the field is no longer optional unless you also remember to add `#[serde(default)]`.\n\nNone of these are bugs that are easy to fix in Serde. They fall out of its design, and that design is protected by Serde’s stability guarantees.\n\nBack in 2022 I started an experiment called [Deser](https://github.com/mitsuhiko/deser). It’s a serialization library for Rust that takes the user experience of [Serde](https://serde.rs/) and puts it on top of a completely different architecture inspired by [miniserde](https://github.com/dtolnay/miniserde). I never really finished it and it sat around for a few years. I picked it back up, and it has now reached a point where I think it’s worth looking at. Even just to inspire others to see if they want to explore the space.\n\n## The Name And Idea\n\nThe name is Serde with its two halves swapped. Deser is Serde but the other way around. In Serde, a type drives the deserialization process: a `Deserialize` impl asks the deserializer for the kind of value it expects, the format calls back into a visitor. Every nested value is handled by recursion which makes Serde deserialization inherently grow the stack with each level of nesting.\n\nDeser on the other hand turns this around and the format tells the type of the next value and pushes events into a sink. When a sink hits the start of a nested value, it doesn’t call into it but hands back a new sink to a driver, which keeps all state on the heap (in fact, in an arena). On the way out, emitters return their nested values instead of recursing into them.\n\nThat also means that Deser cannot support formats like protobuf that are not self describing. They are in fact quite intentionally left out of the design entirely. Which is one way to say: if you want to “fix” Serde, you need to make some other compromises.\n\nMost of the reasons for Deser’s ideas go back to [Sentry Relay](https://github.com/getsentry/relay), which processes enormous amounts of untrusted JSON. Over the years when I was at Sentry we ran into the same set of problems again and again, and many of them are not really bugs in Serde but consequences of its design. Serde’s stability guarantees mean that a lot of them cannot be fixed without breaking every format and every hand written implementation. Most of these problems come from three decisions:\n\n1. **One set of traits for all formats.** Serde serves both self describing formats (JSON, YAML, TOML, …) and formats where the reader has to know the type upfront (postcard, bincode, protobuf, …). That is incredibly useful, but it means that some features only work with some formats, and you find out at runtime. In case of Serde it also has some odd wrinkles where a derived struct quietly accepts an array in place of an object in JSON for instance.\n\n2. **A fixed data model that loses information when buffering.** Internally tagged enums, untagged enums and `flatten` need to buffer values before they know what to do with them. The buffer can’t hold everything the format knew, errors lose their location and extensions to the ecosystem rely on in-band signalling to express things such as arbitrary precision numbers.\n\n3. **Recursion on the call stack.** Every level of nesting uses stack space. Formats protect against this with a recursion limit, but the moment you go through a code path that doesn’t have one (writing, dynamic values), deeply nested data can take down your process. It also means that a deserialization cannot be paused while you wait for more input.\n\nMany of the corresponding Serde issues have been open for years, and I wrote about [abusing Serde](https://lucumr.pocoo.org/2021/11/14/abusing-serde/) before. People have tried different angles on this over the years. Some went minimal and dropped most features to get fast compiles and no recursion. dtolnay’s own [miniserde](https://github.com/dtolnay/miniserde) is the best example of that, and deser’s trait design was originally modelled after it. Other recent attempts went for runtime reflection, or for a new data model with a focus on binary formats.\n\nIf you want to read up on all of the collected challenges with Serde’s design, I maintain [a lengthy list here](https://github.com/mitsuhiko/deser/blob/main/SERDE.md).\n\n## Dethroning Serde\n\nFirst of all I don’t think it’s likely that one can replace Serde. [The orphan rule](https://smallcultfollowing.com/babysteps/blog/2022/04/17/coherence-and-crate-level-where-clauses/) entrenches Serde incredibly well in the ecosystem. But some things are within the reach of a crate author’s control. In case of Deser it’s completeness.\n\nDeser today implements all important self describing formats from YAML, JSON, TOML, CBOR, JSON5 and the likes, but also XML and plist to really close the gap. XML in particular is something Serde has declined to support, and it shows (more on that below). At the very least format support should not be the reason not to use Deser.\n\nThe second problem usually is that actually solving Serde’s issues comes at a significant cost in compile time and/or runtime performance. Deser is no different. While Deser’s compile times are a bit better than Serde’s, the binary bloat is quite a bit worse and the runtime performance is mixed. It’s roughly comparable if you look at the numbers but depending on the format structure you are losing significantly from some of the tradeoffs.\n\nThat said, it’s now in a state where it’s at least in principle a drop-in replacement where the tradeoffs might work well for users.\n\n## Deser’s Design\n\nDeser does not try to be significantly different than Serde on the surface level. For most uses you derive `Serialize` and `Deserialize` and then start using it with your format implementing crate of choice. Most attributes are very similar, though they are taking Rust expressions instead of strings.\n\n```\nuse deser::{Serialize, Deserialize};\n\n#[derive(Debug, Serialize, Deserialize)]\n#[deser(rename_all = \"camelCase\")]\npub struct Account {\n    id: u64,\n    account_holder: String,\n    #[deser(default)]\n    is_deactivated: bool,\n}\n\nlet account: Account = deser_json::from_str(json)?;\n```\n\nThe difference in the design would become more apparent if you implement a serializer or deserializer yourself. Instead of visitors that call into each other recursively, deserializing a type creates a *sink* which receives events that are directly emitted by the parser, and *serializing produces emitters* that hand out values. Nested sinks and emitters are handed back to a driver, which keeps them on the heap. This design, which is entirely stolen from miniserde, gives some interesting consequences:\n\n- **No stack overflows.** You can arbitrarily nest structures without issues. For untrusted input you set limits with a layer, and you pick the number that you are comfortable with, which is independent of your stack space.\n- **Suspendable.** Because the state lives in the driver, a deserialization can be fed input as it arrives. It’s also `Send`, so it can move between threads while you wait on IO which makes it much nicer to use with tokio. Formats like JSON, CBOR and MessagePack can be parsed as a stream if you so desire.\n- **An extensible data model.** The core data model is small and made of atoms, maps and sequences. For all else, there are extension values (`DateTime`, `Uuid`, etc.) that also all carry a fallback for formats that don’t understand them. Unlike Serde this means it does not rely on in-band signalling of objects with magic keys to smuggle values through.\n- **Lossless buffering.** When a value has to be buffered (for instance because the tag of an internally tagged enum comes last), Deser records the events together with everything the format knew about them. Protocol specific extension types or error locations all survive.\n- **Layers** are a middleware system that sit between the format and your types and can track things like paths, enforce safety limits, rename keys or redact values without having to touch specific code paths.\n- **Native flattening** that doesn’t buffer at all.\n\nOn top of that are a lot of things that I just wanted to have:\n\n- Allow enum tags to be of any type, not just strings\n- Adapters that compose (`as = Option<Vec<DisplayFromStr>>`)\n- Enabling validation as an adapter\n- derive attributes that are real Rust expressions instead of strings\n- bytes as a core functionality in the data model\n- duplicate keys rejected by default and errors that point at the problem\n\nHere is a small configuration type that shows a few of these together:\n\n```\nuse deser::adapters::DisplayFromStr;\nuse deser::de::Recording;\nuse deser::{Deserialize, Serialize};\nuse deser_encoding::Hex;\nuse deser_validate::{Check, NonEmpty, Range};\nuse ipnet::IpNet;\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct Config {\n    // at least one 256-bit key, each written as hex\n    #[deser(as = Check<NonEmpty, Vec<Hex>>)]\n    secret_keys: Vec<[u8; 32]>,\n    // `IpNet` knows nothing about deser, but has `FromStr` and `Display`\n    #[deser(as = Option<Vec<DisplayFromStr>>)]\n    allowed_networks: Option<Vec<IpNet>>,\n    listeners: Vec<Listener>,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\n#[deser(tag = \"type\", rename_all = \"snake_case\")]\npub enum Listener {\n    Unix { path: PathBuf },\n    Tcp {\n        host: IpAddr,\n        #[deser(as = Check<Range<1, 65535>>)]\n        port: u16,\n    },\n    // types this version does not know are kept and written back\n    #[deser(other)]\n    Other(#[deser(tag)] String, Recording),\n}\n```\n\nAdapters are types, so `Hex` can go inside a `Vec`, and `DisplayFromStr` inside a `Vec` inside an `Option`. Validators are adapters too, so `Check<NonEmpty, Vec<Hex>>` decodes the keys and then checks that there is at least one. The catch-all variant keeps the tag and a recording of everything else in case someone wants to process it later.\n\nErrors are something I care a lot about, so here is what happens when a value is wrong:\n\n```\nsecret_keys = [\"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\"]\nallowed_networks = [\"10.0.0.0/8\", \"fd00::/8\"]\n\n[[listeners]]\ntype = \"unix\"\npath = \"/run/app.sock\"\n\n[[listeners]]\nhost = \"127.0.0.1\"\nport = 0\ntype = \"tcp\"\n\n[[listeners]]\ntype = \"quic\"\nhost = \"::1\"\nalpn = [\"h3\"]\n```\n\n```\nlet config: Config = deser_toml::Deserializer::from_str(input)\n    .deserialize_with(|driver| driver.push_layer(PathLayer::new()))?;\n```\n\nNote that here the tag of the internally tagged enum comes last which means that the values have to be buffered until the tag is known. In Serde this is tricky and we would lose the location if we used some tricks to add it. With Deser however, with the path layer enabled Deser you where in the structure the problem is:\n\n```\nUnexpected: invalid value: must be between 1 and 65535 at line 10 column 8 (path: listeners[1].port)\n```\n\n## Deser Meta Data\n\nDeser really wants to be extensible, and XML is a more extreme example of the differences between Deser and Serde. Here is an Atom entry that mixes in Dublin Core for the authors:\n\n```\nuse chrono::{DateTime, Utc};\nuse deser::Deserialize;\nuse deser_value::Value;\nuse deser_xml::DeserializerConfig;\n\ndeser_xml::namespace!(\n    atom = \"http://www.w3.org/2005/Atom\",\n    dc = \"http://purl.org/dc/elements/1.1/\",\n);\n\n#[derive(Debug, Deserialize)]\nstruct Entry {\n    #[deser(rename = atom!(\"title\"))]\n    title: String,\n    #[deser(rename = dc!(\"creator\"))]\n    creators: Vec<String>,\n    #[deser(rename = atom!(\"updated\"))]\n    updated: DateTime<Utc>,\n}\n\n// entries we understand, and everything else is kept as it is\n#[derive(Debug, Deserialize)]\n#[deser(untagged)]\nenum Item {\n    Entry(Entry),\n    Other(Value),\n}\n\nlet item: Item = DeserializerConfig::new()\n    .resolve_namespaces(true)\n    .from_str(r#\"\n        <entry xmlns=\"http://www.w3.org/2005/Atom\"\n               xmlns:d=\"http://purl.org/dc/elements/1.1/\">\n          <title>Deser</title>\n          <d:creator>John</d:creator>\n          <updated>2026-09-29T21:00:00Z</updated>\n          <d:creator>Jane</d:creator>\n        </entry>\n    \"#)?;\n```\n\nXML uses namespaces which means that names need to be matched by their namespace, not by the prefix the document happens to use. Here the document says `d:` and the type says `dc!`. `atom!(\"title\")` is just the string `{http://www.w3.org/2005/Atom}title`, which works because attributes are expressions. The two creators are collected into one `Vec` even though there is another element between them, and the text of `updated` goes straight into a `chrono` datetime. Because the enum is untagged, the entry has to be buffered before a variant is picked, and deser’s buffer keeps both creators. So the result is an `Entry` with John and Jane.\n\nquick-xml, the most popular XML crate for Serde, drops the prefixes and ignores namespaces entirely, so a `<x:title>` from some other namespace is happily accepted as the title of the entry. The split list part though is considerably worse. A plain `Entry` fails with a duplicate field error for `creator`, unless you turn on the `overlapped-lists` feature (which, remember, is a global additive flag that any crate could set). That feature makes quick-xml read ahead to the end of the element and buffer everything in between, without a limit unless you set one.\n\nBut the feature only helps when quick-xml is hooked up to the struct directly and no buffering is taking place. Wrap the struct in the untagged enum and Serde buffers the entry itself. Read from that buffer, `Entry` sees `creator` twice and fails again. The fallback is a map, which keeps only the last `creator`, and there is no error. With or without the feature you get this:\n\n```\nOther({\"creator\": {\"$text\": \"Jane\"}, \"title\": {\"$text\": \"Deser\"}, ...})\n```\n\nNotice how John is gone.\n\nFormat specific extension types such as TOML datetimes are another case. TOML has them natively, Serde’s data model does not, so the `toml` crate passes them on as a map with a magic key. In Deser a datetime is an extension value, which formats that know it keep and all others write as a string:\n\n```\nlet value: Value = deser_toml::from_str(\"released = 2026-09-29T21:00:00+02:00\")?;\n\ndeser_json::to_string(&value)?;\n// {\"released\":\"2026-09-29T21:00:00+02:00\"}\ndeser_toml::to_string(&value)?;\n// released = 2026-09-29T21:00:00+02:00\n```\n\nThe same with `serde_json::Value` gives you `{\"released\":{\"$__toml_private_datetime\":\"2026-09-29T21:00:00+02:00\"}}`, and reading the value into a `chrono::DateTime` fails outright with `invalid type: map, expected an RFC 3339 formatted date and time string`.\n\n## The Cost\n\nSo now that you know Deser is at least in theory cool, at what cost?\n\nIt is not free. The design relies on dynamic dispatch and on sinks and emitters that live on the heap, and that has considerable runtime overhead. In my own measurements for JSON, Deser reads somewhere between 33% faster and 60% slower than `serde_json depending` on the data. On average it’s about 10% slower for reading. Writes are between three times as fast and 70% slower and a wash on average. For YAML and TOML it’s noticeably faster than the Serde based crates, but that is more about the format implementations than the architecture.\n\nCompile times slightly are better, but not dramatically so. Because it doesn’t monomorphize everything, release builds of derived code are about 2.3 times as fast as with Serde and that get a tiny bit better in practice for your own code as less recompilation is necessary.\n\nTo make Deser’s design work at all, it also uses `unsafe` internally. Most of this is to keep the chain of borrowed sinks on the heap. I feel like this is fine in the days of Miri and agents, but I know it makes some folks uneasy.\n\nAnd well, the biggest cost is that it’s just not Serde.\n\n## How Much Is There?\n\nQuite a lot actually which might be surprising. In addition to the core there is support for [derive](https://github.com/mitsuhiko/deser/tree/main/deser-derive).\n\nIt supports all flavorts of JSON you can think of: [JSON](https://github.com/mitsuhiko/deser/tree/main/deser-json), [JSONC](https://github.com/mitsuhiko/deser/tree/main/deser-jsonc), [JSON5](https://github.com/mitsuhiko/deser/tree/main/deser-json5) and [HJSON](https://github.com/mitsuhiko/deser/tree/main/deser-hj). (Fun fact here: they are all generated out of [one shared parser template](https://github.com/mitsuhiko/deser/tree/main/deser-template-json)) For binary handling it supports [CBOR](https://github.com/mitsuhiko/deser/tree/main/deser-cbor) and [MessagePack](https://github.com/mitsuhiko/deser/tree/main/deser-msgpack). Additionally it does [YAML](https://github.com/mitsuhiko/deser/tree/main/deser-yaml) 1.1 and 1.2, [TOML](https://github.com/mitsuhiko/deser/tree/main/deser-toml), [XML](https://github.com/mitsuhiko/deser/tree/main/deser-xml) and all three flavors of Apple’s [plist](https://github.com/mitsuhiko/deser/tree/main/deser-plist) as well as [CSV/TSV](https://github.com/mitsuhiko/deser/tree/main/deser-csv), [urlencoded data](https://github.com/mitsuhiko/deser/tree/main/deser-urlencoded) and [environment variables](https://github.com/mitsuhiko/deser/tree/main/deser-env). For more crazy contraptions you can [attach path info](https://github.com/mitsuhiko/deser/tree/main/deser-path) or [capture location data](https://github.com/mitsuhiko/deser/tree/main/deser-location) as well as support for [debug printing](https://github.com/mitsuhiko/deser/tree/main/deser-debug). You can perform [validation](https://github.com/mitsuhiko/deser/tree/main/deser-validate) as you parse, opt into different [binary encodings](https://github.com/mitsuhiko/deser/tree/main/deser-encoding) in addition to base64, you can [bridge to serde](https://github.com/mitsuhiko/deser/tree/main/deser-serde) or capture [dynamic values](https://github.com/mitsuhiko/deser/tree/main/deser-value), [transcode](https://github.com/mitsuhiko/deser/tree/main/deser-transcode) between formats or hook it up with [tokio](https://github.com/mitsuhiko/deser/tree/main/deser-tokio).\n\nFor documentation see [docs.rs/deser](https://docs.rs/deser/latest/deser/) and the code itself is [on GitHub](https://github.com/mitsuhiko/deser) alongside [many examples](https://github.com/mitsuhiko/deser/tree/main/examples).\n\nThis entry was tagged [rust](https://lucumr.pocoo.org/tags/rust/)\n\n[copy as](https://lucumr.pocoo.org/2026/9/29/deser.md) / [view](https://lucumr.pocoo.org/2026/9/29/deser.md) markdown\n",
  "authors": [
    "Armin Ronacher"
  ],
  "captured_at": "2026-09-29T23:14:03.129222144+00:00",
  "category": "programming",
  "content_html": "<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://serde.rs/\">Serde</a> is an amazing serialization library for Rust and it has been a huge reason why I felt productive with it for years. However already while at Sentry I got quite frustrated with some of the limitations with it but actually replacing Serde is tricky because of the might that it has in the ecosystem. Also because it’s quite hard to actually do better without also making some potentially painful compromises.</p>\n<p>Here are three examples of Serde corner cases that show poor interactions of Serde features or unexpected limitations:</p>\n<p>A number that is a map</p>\n<p>An internally tagged enum, with <code>serde_json</code>‘s <code>arbitrary_precision</code> feature turned on:</p>\n<pre><code><span class=\"code-snippet\">#[derive(Deserialize)]\n#[serde(tag = &quot;type&quot;)]\nenum Shape {\n    Circle { radius: f64 },\n}\n\nserde_json::from_str::&lt;Shape&gt;(r#&quot;{&quot;type&quot;: &quot;Circle&quot;, &quot;radius&quot;: 1.5}&quot;#)\n// error: invalid type: map, expected f64\n</span></code></pre>\n<p>Serde’s data model has no place for arbitrary precision numbers, so <code>serde_json</code> uses in-band signalling with a map with a magic key. The enum has to buffer the fields until it has seen the tag, and the buffer does not know about the magic key. Because Cargo features are unified, it’s enough for any crate in your dependency graph to turn the feature on.</p>\n<p>Flattening breaks integer keys</p>\n<pre><code><span class=\"code-snippet\">#[derive(Deserialize)]\nstruct Stats {\n    scores: HashMap&lt;u32, u32&gt;,\n}\n\n#[derive(Deserialize)]\nstruct Report {\n    name: String,\n    #[serde(flatten)]\n    stats: Stats,\n}\n\nserde_json::from_str::&lt;Report&gt;(r#&quot;{&quot;name&quot;: &quot;x&quot;, &quot;scores&quot;: {&quot;42&quot;: 23}}&quot;#)\n// error: invalid type: string &quot;42&quot;, expected u32 at line 1 column 35\n</span></code></pre>\n<p><code>Stats</code> on its own parses <code>{&quot;scores&quot;: {&quot;42&quot;: 23}}</code> just fine. JSON keys are always strings, and <code>serde_json</code> only turns them into integers if the type asks for one. However once <code>flatten</code> buffers the value, <code>&quot;42&quot;</code> is just a string. The error also points at the end of the document rather than at the key.</p>\n<p>Adapters do not compose</p>\n<pre><code><span class=\"code-snippet\" data-language=\"Rust\"><span class=\"syntax-source syntax-rust\"><span class=\"syntax-meta syntax-function syntax-rust\"><span class=\"syntax-meta syntax-function syntax-rust\"><span class=\"syntax-storage syntax-type syntax-function syntax-rust\">fn</span> </span><span class=\"syntax-entity syntax-name syntax-function syntax-rust\">from_hex</span></span><span class=\"syntax-meta syntax-generic syntax-rust\"><span class=\"syntax-punctuation syntax-definition syntax-generic syntax-begin syntax-rust\">&lt;</span><span class=\"syntax-storage syntax-modifier syntax-lifetime syntax-rust\">&#39;de</span>, D<span class=\"syntax-punctuation syntax-separator syntax-rust\">:</span> <span class=\"syntax-meta syntax-generic syntax-rust\">Deserializer<span class=\"syntax-punctuation syntax-definition syntax-generic syntax-begin syntax-rust\">&lt;</span><span class=\"syntax-storage syntax-modifier syntax-lifetime syntax-rust\">&#39;de</span><span class=\"syntax-punctuation syntax-definition syntax-generic syntax-end syntax-rust\">&gt;</span></span><span class=\"syntax-punctuation syntax-definition syntax-generic syntax-end syntax-rust\">&gt;</span></span><span class=\"syntax-meta syntax-function syntax-rust\"><span class=\"syntax-meta syntax-function syntax-parameters syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-parameters syntax-begin syntax-rust\">(</span><span class=\"syntax-variable syntax-parameter syntax-rust\">d</span><span class=\"syntax-punctuation syntax-separator syntax-rust\">:</span> D</span><span class=\"syntax-meta syntax-function syntax-rust\"><span class=\"syntax-meta syntax-function syntax-parameters syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-parameters syntax-end syntax-rust\">)</span></span></span></span><span class=\"syntax-meta syntax-function syntax-rust\"> <span class=\"syntax-meta syntax-function syntax-return-type syntax-rust\"><span class=\"syntax-punctuation syntax-separator syntax-rust\">-&gt;</span> <span class=\"syntax-meta syntax-generic syntax-rust\">Result<span class=\"syntax-punctuation syntax-definition syntax-generic syntax-begin syntax-rust\">&lt;</span><span class=\"syntax-storage syntax-type syntax-rust\">u32</span>, <span class=\"syntax-meta syntax-path syntax-rust\"><span class=\"syntax-storage syntax-type syntax-rust\"><span class=\"syntax-storage syntax-type syntax-rust\">D</span><span class=\"syntax-punctuation syntax-accessor syntax-rust\">::</span></span></span>Error<span class=\"syntax-punctuation syntax-definition syntax-generic syntax-end syntax-rust\">&gt;</span></span></span> </span><span class=\"syntax-meta syntax-function syntax-rust\"><span class=\"syntax-meta syntax-block syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-block syntax-begin syntax-rust\">{</span> <span class=\"syntax-keyword syntax-operator syntax-rust\">...</span> </span><span class=\"syntax-meta syntax-block syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-block syntax-end syntax-rust\">}</span></span></span>\n\n<span class=\"syntax-meta syntax-annotation syntax-rust\"><span class=\"syntax-punctuation syntax-definition syntax-annotation syntax-rust\">#</span><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-rust\">[</span><span class=\"syntax-variable syntax-annotation syntax-rust\">derive</span><span class=\"syntax-meta syntax-annotation syntax-parameters syntax-rust\"><span class=\"syntax-meta syntax-group syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-rust\">(</span></span></span><span class=\"syntax-meta syntax-annotation syntax-parameters syntax-rust\"><span class=\"syntax-meta syntax-group syntax-rust\">Deserialize</span></span><span class=\"syntax-meta syntax-annotation syntax-parameters syntax-rust\"><span class=\"syntax-meta syntax-group syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-rust\">)</span></span></span><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-rust\">]</span></span>\n<span class=\"syntax-meta syntax-struct syntax-rust\"><span class=\"syntax-storage syntax-type syntax-struct syntax-rust\">struct</span> </span><span class=\"syntax-meta syntax-struct syntax-rust\"><span class=\"syntax-entity syntax-name syntax-struct syntax-rust\">Theme</span> </span><span class=\"syntax-meta syntax-struct syntax-rust\"><span class=\"syntax-meta syntax-block syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-block syntax-begin syntax-rust\">{</span>\n    #[serde(deserialize_with = &quot;from_hex&quot;)]\n    <span class=\"syntax-variable syntax-other syntax-member syntax-rust\">primary</span><span class=\"syntax-punctuation syntax-separator syntax-rust\">:</span> <span class=\"syntax-storage syntax-type syntax-rust\">u32</span>,\n    #[serde(deserialize_with = &quot;from_hex&quot;)]\n    <span class=\"syntax-variable syntax-other syntax-member syntax-rust\">accent</span><span class=\"syntax-punctuation syntax-separator syntax-rust\">:</span> <span class=\"syntax-meta syntax-generic syntax-rust\">Option<span class=\"syntax-punctuation syntax-definition syntax-generic syntax-begin syntax-rust\">&lt;</span><span class=\"syntax-storage syntax-type syntax-rust\">u32</span><span class=\"syntax-punctuation syntax-definition syntax-generic syntax-end syntax-rust\">&gt;</span></span>,\n</span><span class=\"syntax-meta syntax-block syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-block syntax-end syntax-rust\">}</span></span></span>\n\n<span class=\"syntax-comment syntax-line syntax-double-slash syntax-rust\"><span class=\"syntax-punctuation syntax-definition syntax-comment syntax-rust\">//</span>error[E0308]: `?` operator has incompatible types\n</span><span class=\"syntax-comment syntax-line syntax-double-slash syntax-rust\"><span class=\"syntax-punctuation syntax-definition syntax-comment syntax-rust\">//</span>  |\n</span><span class=\"syntax-comment syntax-line syntax-double-slash syntax-rust\"><span class=\"syntax-punctuation syntax-definition syntax-comment syntax-rust\">//</span>  |     #[serde(deserialize_with = &quot;from_hex&quot;)]\n</span><span class=\"syntax-comment syntax-line syntax-double-slash syntax-rust\"><span class=\"syntax-punctuation syntax-definition syntax-comment syntax-rust\">//</span>  |                                ^^^^^^^^^^ expected `Option&lt;u32&gt;`, found `u32`\n</span><span class=\"syntax-comment syntax-line syntax-double-slash syntax-rust\"><span class=\"syntax-punctuation syntax-definition syntax-comment syntax-rust\">//</span>  |\n</span><span class=\"syntax-comment syntax-line syntax-double-slash syntax-rust\"><span class=\"syntax-punctuation syntax-definition syntax-comment syntax-rust\">//</span>help: try wrapping the expression in `Some`\n</span><span class=\"syntax-comment syntax-line syntax-double-slash syntax-rust\"><span class=\"syntax-punctuation syntax-definition syntax-comment syntax-rust\">//</span>  |\n</span><span class=\"syntax-comment syntax-line syntax-double-slash syntax-rust\"><span class=\"syntax-punctuation syntax-definition syntax-comment syntax-rust\">//</span>  |     #[serde(deserialize_with = Some(&quot;from_hex&quot;))]\n</span><span class=\"syntax-comment syntax-line syntax-double-slash syntax-rust\"><span class=\"syntax-punctuation syntax-definition syntax-comment syntax-rust\">//</span>  |                                +++++          +\n</span></span></span></code></pre>\n<p>A function cannot be passed as a type parameter, so there is no way to apply <code>from_hex</code> to the inside of an <code>Option</code>, a <code>Vec</code> or a map. You write another function for every wrapper, and once you have <code>from_opt_hex</code> the field is no longer optional unless you also remember to add <code>#[serde(default)]</code>.</p>\n<p>None of these are bugs that are easy to fix in Serde. They fall out of its design, and that design is protected by Serde’s stability guarantees.</p>\n<p>Back in 2022 I started an experiment called <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser\">Deser</a>. It’s a serialization library for Rust that takes the user experience of <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://serde.rs/\">Serde</a> and puts it on top of a completely different architecture inspired by <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/dtolnay/miniserde\">miniserde</a>. I never really finished it and it sat around for a few years. I picked it back up, and it has now reached a point where I think it’s worth looking at. Even just to inspire others to see if they want to explore the space.</p>\n<h2>The Name And Idea</h2>\n<p>The name is Serde with its two halves swapped. Deser is Serde but the other way around. In Serde, a type drives the deserialization process: a <code>Deserialize</code> impl asks the deserializer for the kind of value it expects, the format calls back into a visitor. Every nested value is handled by recursion which makes Serde deserialization inherently grow the stack with each level of nesting.</p>\n<p>Deser on the other hand turns this around and the format tells the type of the next value and pushes events into a sink. When a sink hits the start of a nested value, it doesn’t call into it but hands back a new sink to a driver, which keeps all state on the heap (in fact, in an arena). On the way out, emitters return their nested values instead of recursing into them.</p>\n<p>That also means that Deser cannot support formats like protobuf that are not self describing. They are in fact quite intentionally left out of the design entirely. Which is one way to say: if you want to “fix” Serde, you need to make some other compromises.</p>\n<p>Most of the reasons for Deser’s ideas go back to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/getsentry/relay\">Sentry Relay</a>, which processes enormous amounts of untrusted JSON. Over the years when I was at Sentry we ran into the same set of problems again and again, and many of them are not really bugs in Serde but consequences of its design. Serde’s stability guarantees mean that a lot of them cannot be fixed without breaking every format and every hand written implementation. Most of these problems come from three decisions:</p>\n<ol>\n<li>\n<p><strong>One set of traits for all formats.</strong> Serde serves both self describing formats (JSON, YAML, TOML, …) and formats where the reader has to know the type upfront (postcard, bincode, protobuf, …). That is incredibly useful, but it means that some features only work with some formats, and you find out at runtime. In case of Serde it also has some odd wrinkles where a derived struct quietly accepts an array in place of an object in JSON for instance.</p>\n</li>\n<li>\n<p><strong>A fixed data model that loses information when buffering.</strong> Internally tagged enums, untagged enums and <code>flatten</code> need to buffer values before they know what to do with them. The buffer can’t hold everything the format knew, errors lose their location and extensions to the ecosystem rely on in-band signalling to express things such as arbitrary precision numbers.</p>\n</li>\n<li>\n<p><strong>Recursion on the call stack.</strong> Every level of nesting uses stack space. Formats protect against this with a recursion limit, but the moment you go through a code path that doesn’t have one (writing, dynamic values), deeply nested data can take down your process. It also means that a deserialization cannot be paused while you wait for more input.</p>\n</li>\n</ol>\n<p>Many of the corresponding Serde issues have been open for years, and I wrote about <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://lucumr.pocoo.org/2021/11/14/abusing-serde/\">abusing Serde</a> before. People have tried different angles on this over the years. Some went minimal and dropped most features to get fast compiles and no recursion. dtolnay’s own <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/dtolnay/miniserde\">miniserde</a> is the best example of that, and deser’s trait design was originally modelled after it. Other recent attempts went for runtime reflection, or for a new data model with a focus on binary formats.</p>\n<p>If you want to read up on all of the collected challenges with Serde’s design, I maintain <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/blob/main/SERDE.md\">a lengthy list here</a>.</p>\n<h2>Dethroning Serde</h2>\n<p>First of all I don’t think it’s likely that one can replace Serde. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://smallcultfollowing.com/babysteps/blog/2022/04/17/coherence-and-crate-level-where-clauses/\">The orphan rule</a> entrenches Serde incredibly well in the ecosystem. But some things are within the reach of a crate author’s control. In case of Deser it’s completeness.</p>\n<p>Deser today implements all important self describing formats from YAML, JSON, TOML, CBOR, JSON5 and the likes, but also XML and plist to really close the gap. XML in particular is something Serde has declined to support, and it shows (more on that below). At the very least format support should not be the reason not to use Deser.</p>\n<p>The second problem usually is that actually solving Serde’s issues comes at a significant cost in compile time and/or runtime performance. Deser is no different. While Deser’s compile times are a bit better than Serde’s, the binary bloat is quite a bit worse and the runtime performance is mixed. It’s roughly comparable if you look at the numbers but depending on the format structure you are losing significantly from some of the tradeoffs.</p>\n<p>That said, it’s now in a state where it’s at least in principle a drop-in replacement where the tradeoffs might work well for users.</p>\n<h2>Deser’s Design</h2>\n<p>Deser does not try to be significantly different than Serde on the surface level. For most uses you derive <code>Serialize</code> and <code>Deserialize</code> and then start using it with your format implementing crate of choice. Most attributes are very similar, though they are taking Rust expressions instead of strings.</p>\n<pre><code><span class=\"code-snippet\" data-language=\"Rust\"><span class=\"syntax-source syntax-rust\"><span class=\"syntax-keyword syntax-other syntax-rust\">use</span> <span class=\"syntax-meta syntax-path syntax-rust\">deser<span class=\"syntax-punctuation syntax-accessor syntax-rust\">::</span></span><span class=\"syntax-meta syntax-block syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-block syntax-begin syntax-rust\">{</span>Serialize<span class=\"syntax-punctuation syntax-separator syntax-rust\">,</span> Deserialize</span><span class=\"syntax-meta syntax-block syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-block syntax-end syntax-rust\">}</span></span><span class=\"syntax-punctuation syntax-terminator syntax-rust\">;</span>\n\n<span class=\"syntax-meta syntax-annotation syntax-rust\"><span class=\"syntax-punctuation syntax-definition syntax-annotation syntax-rust\">#</span><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-rust\">[</span><span class=\"syntax-variable syntax-annotation syntax-rust\">derive</span><span class=\"syntax-meta syntax-annotation syntax-parameters syntax-rust\"><span class=\"syntax-meta syntax-group syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-rust\">(</span></span></span><span class=\"syntax-meta syntax-annotation syntax-parameters syntax-rust\"><span class=\"syntax-meta syntax-group syntax-rust\">Debug<span class=\"syntax-punctuation syntax-separator syntax-rust\">,</span> Serialize<span class=\"syntax-punctuation syntax-separator syntax-rust\">,</span> Deserialize</span></span><span class=\"syntax-meta syntax-annotation syntax-parameters syntax-rust\"><span class=\"syntax-meta syntax-group syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-rust\">)</span></span></span><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-rust\">]</span></span>\n<span class=\"syntax-meta syntax-annotation syntax-rust\"><span class=\"syntax-punctuation syntax-definition syntax-annotation syntax-rust\">#</span><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-rust\">[</span><span class=\"syntax-variable syntax-annotation syntax-rust\">deser</span><span class=\"syntax-meta syntax-annotation syntax-parameters syntax-rust\"><span class=\"syntax-meta syntax-group syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-rust\">(</span></span></span><span class=\"syntax-meta syntax-annotation syntax-parameters syntax-rust\"><span class=\"syntax-meta syntax-group syntax-rust\">rename_all <span class=\"syntax-keyword syntax-operator syntax-rust\">=</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-rust\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-rust\">&quot;</span>camelCase<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-rust\">&quot;</span></span></span></span><span class=\"syntax-meta syntax-annotation syntax-parameters syntax-rust\"><span class=\"syntax-meta syntax-group syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-rust\">)</span></span></span><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-rust\">]</span></span>\n<span class=\"syntax-meta syntax-struct syntax-rust\"><span class=\"syntax-storage syntax-modifier syntax-rust\">pub</span> <span class=\"syntax-storage syntax-type syntax-struct syntax-rust\">struct</span> </span><span class=\"syntax-meta syntax-struct syntax-rust\"><span class=\"syntax-entity syntax-name syntax-struct syntax-rust\">Account</span> </span><span class=\"syntax-meta syntax-struct syntax-rust\"><span class=\"syntax-meta syntax-block syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-block syntax-begin syntax-rust\">{</span>\n    <span class=\"syntax-variable syntax-other syntax-member syntax-rust\">id</span><span class=\"syntax-punctuation syntax-separator syntax-rust\">:</span> <span class=\"syntax-storage syntax-type syntax-rust\">u64</span>,\n    <span class=\"syntax-variable syntax-other syntax-member syntax-rust\">account_holder</span><span class=\"syntax-punctuation syntax-separator syntax-rust\">:</span> String,\n    #[deser(default)]\n    <span class=\"syntax-variable syntax-other syntax-member syntax-rust\">is_deactivated</span><span class=\"syntax-punctuation syntax-separator syntax-rust\">:</span> <span class=\"syntax-storage syntax-type syntax-rust\">bool</span>,\n</span><span class=\"syntax-meta syntax-block syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-block syntax-end syntax-rust\">}</span></span></span>\n\n<span class=\"syntax-storage syntax-type syntax-rust\">let</span> account<span class=\"syntax-punctuation syntax-separator syntax-rust\">:</span> Account <span class=\"syntax-keyword syntax-operator syntax-rust\">=</span> <span class=\"syntax-meta syntax-path syntax-rust\">deser_json<span class=\"syntax-punctuation syntax-accessor syntax-rust\">::</span></span>from_str<span class=\"syntax-meta syntax-group syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-rust\">(</span>json</span><span class=\"syntax-meta syntax-group syntax-rust\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-rust\">)</span></span><span class=\"syntax-keyword syntax-operator syntax-rust\">?</span><span class=\"syntax-punctuation syntax-terminator syntax-rust\">;</span>\n</span></span></code></pre>\n<p>The difference in the design would become more apparent if you implement a serializer or deserializer yourself. Instead of visitors that call into each other recursively, deserializing a type creates a <em>sink</em> which receives events that are directly emitted by the parser, and <em>serializing produces emitters</em> that hand out values. Nested sinks and emitters are handed back to a driver, which keeps them on the heap. This design, which is entirely stolen from miniserde, gives some interesting consequences:</p>\n<ul>\n<li><strong>No stack overflows.</strong> You can arbitrarily nest structures without issues. For untrusted input you set limits with a layer, and you pick the number that you are comfortable with, which is independent of your stack space.</li>\n<li><strong>Suspendable.</strong> Because the state lives in the driver, a deserialization can be fed input as it arrives. It’s also <code>Send</code>, so it can move between threads while you wait on IO which makes it much nicer to use with tokio. Formats like JSON, CBOR and MessagePack can be parsed as a stream if you so desire.</li>\n<li><strong>An extensible data model.</strong> The core data model is small and made of atoms, maps and sequences. For all else, there are extension values (<code>DateTime</code>, <code>Uuid</code>, etc.) that also all carry a fallback for formats that don’t understand them. Unlike Serde this means it does not rely on in-band signalling of objects with magic keys to smuggle values through.</li>\n<li><strong>Lossless buffering.</strong> When a value has to be buffered (for instance because the tag of an internally tagged enum comes last), Deser records the events together with everything the format knew about them. Protocol specific extension types or error locations all survive.</li>\n<li><strong>Layers</strong> are a middleware system that sit between the format and your types and can track things like paths, enforce safety limits, rename keys or redact values without having to touch specific code paths.</li>\n<li><strong>Native flattening</strong> that doesn’t buffer at all.</li>\n</ul>\n<p>On top of that are a lot of things that I just wanted to have:</p>\n<ul>\n<li>Allow enum tags to be of any type, not just strings</li>\n<li>Adapters that compose (<code>as = Option&lt;Vec&lt;DisplayFromStr&gt;&gt;</code>)</li>\n<li>Enabling validation as an adapter</li>\n<li>derive attributes that are real Rust expressions instead of strings</li>\n<li>bytes as a core functionality in the data model</li>\n<li>duplicate keys rejected by default and errors that point at the problem</li>\n</ul>\n<p>Here is a small configuration type that shows a few of these together:</p>\n<pre><code><span class=\"code-snippet\" data-language=\"CSS\"><span class=\"syntax-source syntax-css\"><span class=\"syntax-meta syntax-selector syntax-css\"><span class=\"syntax-entity syntax-name syntax-tag syntax-css\">use</span> deser<span class=\"syntax-entity syntax-other syntax-pseudo-element syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">::</span>adapters</span><span class=\"syntax-entity syntax-other syntax-pseudo-element syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">::</span>DisplayFromStr</span>;\n<span class=\"syntax-entity syntax-name syntax-tag syntax-css\">use</span> deser<span class=\"syntax-entity syntax-other syntax-pseudo-element syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">::</span>de</span><span class=\"syntax-entity syntax-other syntax-pseudo-element syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">::</span>Recording</span>;\n<span class=\"syntax-entity syntax-name syntax-tag syntax-css\">use</span> deser::</span><span class=\"syntax-meta syntax-property-list syntax-css\"><span class=\"syntax-punctuation syntax-section syntax-property-list syntax-css\">{</span>D<span class=\"syntax-meta syntax-property-name syntax-css\">eserialize</span>, S<span class=\"syntax-meta syntax-property-name syntax-css\">erialize</span></span><span class=\"syntax-punctuation syntax-section syntax-property-list syntax-css\">}</span>;\n<span class=\"syntax-meta syntax-selector syntax-css\"><span class=\"syntax-entity syntax-name syntax-tag syntax-css\">use</span> deser_encoding<span class=\"syntax-entity syntax-other syntax-pseudo-element syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">::</span>Hex</span>;\n<span class=\"syntax-entity syntax-name syntax-tag syntax-css\">use</span> deser_validate::</span><span class=\"syntax-meta syntax-property-list syntax-css\"><span class=\"syntax-punctuation syntax-section syntax-property-list syntax-css\">{</span>C<span class=\"syntax-meta syntax-property-name syntax-css\">heck</span>, N<span class=\"syntax-meta syntax-property-name syntax-css\">on</span>E<span class=\"syntax-meta syntax-property-name syntax-css\">mpty</span>, R<span class=\"syntax-meta syntax-property-name syntax-css\">ange</span></span><span class=\"syntax-punctuation syntax-section syntax-property-list syntax-css\">}</span>;\n<span class=\"syntax-meta syntax-selector syntax-css\"><span class=\"syntax-entity syntax-name syntax-tag syntax-css\">use</span> ipnet<span class=\"syntax-entity syntax-other syntax-pseudo-element syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">::</span>IpNet</span>;\n\n#<span class=\"syntax-meta syntax-attribute-selector syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">[</span><span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">derive</span>(<span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">Debug</span>, <span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">Serialize</span>, <span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">Deserialize</span>)<span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">]</span></span>\npub struct Config </span><span class=\"syntax-meta syntax-property-list syntax-css\"><span class=\"syntax-punctuation syntax-section syntax-property-list syntax-css\">{</span>\n    // <span class=\"syntax-meta syntax-property-name syntax-css\">at</span> <span class=\"syntax-meta syntax-property-name syntax-css\">least</span> <span class=\"syntax-meta syntax-property-name syntax-css\">one</span> 256<span class=\"syntax-meta syntax-property-name syntax-css\">-bit</span> <span class=\"syntax-meta syntax-property-name syntax-css\">key</span>, <span class=\"syntax-meta syntax-property-name syntax-css\">each</span> <span class=\"syntax-meta syntax-property-name syntax-css\">written</span> <span class=\"syntax-meta syntax-property-name syntax-css\">as</span> <span class=\"syntax-meta syntax-property-name syntax-css\">hex</span>\n    #[<span class=\"syntax-meta syntax-property-name syntax-css\">deser</span>(<span class=\"syntax-meta syntax-property-name syntax-css\">as</span> = C<span class=\"syntax-meta syntax-property-name syntax-css\">heck</span>&lt;N<span class=\"syntax-meta syntax-property-name syntax-css\">on</span>E<span class=\"syntax-meta syntax-property-name syntax-css\">mpty</span>, V<span class=\"syntax-meta syntax-property-name syntax-css\">ec</span>&lt;H<span class=\"syntax-meta syntax-property-name syntax-css\">ex</span>&gt;&gt;)]\n    <span class=\"syntax-meta syntax-property-name syntax-css\">secret</span>_<span class=\"syntax-meta syntax-property-name syntax-css\">keys</span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-css\">:</span><span class=\"syntax-meta syntax-property-value syntax-css\"> </span><span class=\"syntax-meta syntax-property-value syntax-css\">Vec&lt;[u<span class=\"syntax-constant syntax-numeric syntax-css\">8</span></span><span class=\"syntax-punctuation syntax-terminator syntax-rule syntax-css\">;</span> 32]&gt;,\n    // `I<span class=\"syntax-meta syntax-property-name syntax-css\">p</span>N<span class=\"syntax-meta syntax-property-name syntax-css\">et</span>` <span class=\"syntax-meta syntax-property-name syntax-css\">knows</span> <span class=\"syntax-meta syntax-property-name syntax-css\">nothing</span> <span class=\"syntax-meta syntax-property-name syntax-css\">about</span> <span class=\"syntax-meta syntax-property-name syntax-css\">deser</span>, <span class=\"syntax-meta syntax-property-name syntax-css\">but</span> <span class=\"syntax-meta syntax-property-name syntax-css\">has</span> `F<span class=\"syntax-meta syntax-property-name syntax-css\">rom</span>S<span class=\"syntax-meta syntax-property-name syntax-css\">tr</span>` <span class=\"syntax-meta syntax-property-name syntax-css\">and</span> `D<span class=\"syntax-meta syntax-property-name syntax-css\">isplay</span>`\n    #[<span class=\"syntax-meta syntax-property-name syntax-css\">deser</span>(<span class=\"syntax-meta syntax-property-name syntax-css\">as</span> = O<span class=\"syntax-meta syntax-property-name syntax-css\">ption</span>&lt;V<span class=\"syntax-meta syntax-property-name syntax-css\">ec</span>&lt;D<span class=\"syntax-meta syntax-property-name syntax-css\">isplay</span>F<span class=\"syntax-meta syntax-property-name syntax-css\">rom</span>S<span class=\"syntax-meta syntax-property-name syntax-css\">tr</span>&gt;&gt;)]\n    <span class=\"syntax-meta syntax-property-name syntax-css\">allowed</span>_<span class=\"syntax-meta syntax-property-name syntax-css\">networks</span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-css\">:</span><span class=\"syntax-meta syntax-property-value syntax-css\"> </span><span class=\"syntax-meta syntax-property-value syntax-css\">Option&lt;Vec&lt;IpNet&gt;&gt;,\n    listeners: Vec&lt;Listener&gt;,\n</span></span><span class=\"syntax-punctuation syntax-section syntax-property-list syntax-css\">}</span>\n\n<span class=\"syntax-meta syntax-selector syntax-css\">#<span class=\"syntax-meta syntax-attribute-selector syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">[</span><span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">derive</span>(<span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">Debug</span>, <span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">Serialize</span>, <span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">Deserialize</span>)<span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">]</span></span>\n#<span class=\"syntax-meta syntax-attribute-selector syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">[</span><span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">deser</span>(<span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">tag</span> <span class=\"syntax-keyword syntax-operator syntax-attribute-selector syntax-css\">=</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-css\">&quot;</span>type<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-css\">&quot;</span></span><span class=\"syntax-string syntax-unquoted syntax-css\">,</span> <span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">rename_all</span> <span class=\"syntax-keyword syntax-operator syntax-attribute-selector syntax-css\">=</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-css\">&quot;</span>snake_case<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-css\">&quot;</span></span><span class=\"syntax-string syntax-unquoted syntax-css\">)</span><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">]</span></span>\npub enum Listener </span><span class=\"syntax-meta syntax-property-list syntax-css\"><span class=\"syntax-punctuation syntax-section syntax-property-list syntax-css\">{</span>\n    U<span class=\"syntax-meta syntax-property-name syntax-css\">nix</span> { <span class=\"syntax-meta syntax-property-name syntax-css\">path</span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-css\">:</span><span class=\"syntax-meta syntax-property-value syntax-css\"> </span><span class=\"syntax-meta syntax-property-value syntax-css\">PathBuf </span></span><span class=\"syntax-punctuation syntax-section syntax-property-list syntax-css\">}</span>,\n<span class=\"syntax-meta syntax-selector syntax-css\">    Tcp </span><span class=\"syntax-meta syntax-property-list syntax-css\"><span class=\"syntax-punctuation syntax-section syntax-property-list syntax-css\">{</span>\n        <span class=\"syntax-meta syntax-property-name syntax-css\">host</span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-css\">:</span><span class=\"syntax-meta syntax-property-value syntax-css\"> </span><span class=\"syntax-meta syntax-property-value syntax-css\">IpAddr,\n        #[deser(as = Check&lt;Range&lt;<span class=\"syntax-constant syntax-numeric syntax-css\">1</span>, <span class=\"syntax-constant syntax-numeric syntax-css\">65535</span>&gt;&gt;</span>)]\n        <span class=\"syntax-meta syntax-property-name syntax-css\">port</span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-css\">:</span><span class=\"syntax-meta syntax-property-value syntax-css\"> </span><span class=\"syntax-meta syntax-property-value syntax-css\">u<span class=\"syntax-constant syntax-numeric syntax-css\">16</span>,\n    </span></span><span class=\"syntax-punctuation syntax-section syntax-property-list syntax-css\">}</span>,\n    //<span class=\"syntax-meta syntax-selector syntax-css\"> types this version does not know are kept and written back\n    #<span class=\"syntax-meta syntax-attribute-selector syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">[</span><span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">deser</span>(<span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">other</span>)<span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">]</span></span>\n    Other(#<span class=\"syntax-meta syntax-attribute-selector syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">[</span><span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">deser</span>(<span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">tag</span>)<span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">]</span></span> String, Recording</span>),\n}\n</span></span></code></pre>\n<p>Adapters are types, so <code>Hex</code> can go inside a <code>Vec</code>, and <code>DisplayFromStr</code> inside a <code>Vec</code> inside an <code>Option</code>. Validators are adapters too, so <code>Check&lt;NonEmpty, Vec&lt;Hex&gt;&gt;</code> decodes the keys and then checks that there is at least one. The catch-all variant keeps the tag and a recording of everything else in case someone wants to process it later.</p>\n<p>Errors are something I care a lot about, so here is what happens when a value is wrong:</p>\n<pre><code><span class=\"code-snippet\" data-language=\"TOML\">secret_keys = [&quot;9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08&quot;]\nallowed_networks = [&quot;10.0.0.0/8&quot;, &quot;fd00::/8&quot;]\n\n[[listeners]]\ntype = &quot;unix&quot;\npath = &quot;/run/app.sock&quot;\n\n[[listeners]]\nhost = &quot;127.0.0.1&quot;\nport = 0\ntype = &quot;tcp&quot;\n\n[[listeners]]\ntype = &quot;quic&quot;\nhost = &quot;::1&quot;\nalpn = [&quot;h3&quot;]\n</span></code></pre>\n<pre><code><span class=\"code-snippet\">let config: Config = deser_toml::Deserializer::from_str(input)\n    .deserialize_with(|driver| driver.push_layer(PathLayer::new()))?;\n</span></code></pre>\n<p>Note that here the tag of the internally tagged enum comes last which means that the values have to be buffered until the tag is known. In Serde this is tricky and we would lose the location if we used some tricks to add it. With Deser however, with the path layer enabled Deser you where in the structure the problem is:</p>\n<pre><code><span class=\"code-snippet\">Unexpected: invalid value: must be between 1 and 65535 at line 10 column 8 (path: listeners[1].port)\n</span></code></pre>\n<h2>Deser Meta Data</h2>\n<p>Deser really wants to be extensible, and XML is a more extreme example of the differences between Deser and Serde. Here is an Atom entry that mixes in Dublin Core for the authors:</p>\n<pre><code><span class=\"code-snippet\" data-language=\"CSS\"><span class=\"syntax-source syntax-css\"><span class=\"syntax-meta syntax-selector syntax-css\"><span class=\"syntax-entity syntax-name syntax-tag syntax-css\">use</span> chrono::</span><span class=\"syntax-meta syntax-property-list syntax-css\"><span class=\"syntax-punctuation syntax-section syntax-property-list syntax-css\">{</span>D<span class=\"syntax-meta syntax-property-name syntax-css\">ate</span>T<span class=\"syntax-meta syntax-property-name syntax-css\">ime</span>, U<span class=\"syntax-meta syntax-property-name syntax-css\">tc</span></span><span class=\"syntax-punctuation syntax-section syntax-property-list syntax-css\">}</span>;\n<span class=\"syntax-meta syntax-selector syntax-css\"><span class=\"syntax-entity syntax-name syntax-tag syntax-css\">use</span> deser<span class=\"syntax-entity syntax-other syntax-pseudo-element syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">::</span>Deserialize</span>;\n<span class=\"syntax-entity syntax-name syntax-tag syntax-css\">use</span> deser_value<span class=\"syntax-entity syntax-other syntax-pseudo-element syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">::</span>Value</span>;\n<span class=\"syntax-entity syntax-name syntax-tag syntax-css\">use</span> deser_xml<span class=\"syntax-entity syntax-other syntax-pseudo-element syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">::</span>DeserializerConfig</span>;\n\ndeser_xml<span class=\"syntax-entity syntax-other syntax-pseudo-element syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">::</span>namespace</span>!(\n    atom = &quot;http:</span>//<span class=\"syntax-meta syntax-selector syntax-css\">www<span class=\"syntax-entity syntax-other syntax-attribute-name syntax-class syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">.</span>w3</span><span class=\"syntax-entity syntax-other syntax-attribute-name syntax-class syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">.</span>org</span></span>/2005/<span class=\"syntax-meta syntax-selector syntax-css\">Atom&quot;,\n    dc = &quot;http:</span>//<span class=\"syntax-meta syntax-selector syntax-css\">purl<span class=\"syntax-entity syntax-other syntax-attribute-name syntax-class syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">.</span>org</span></span>/<span class=\"syntax-meta syntax-selector syntax-css\">dc</span>/<span class=\"syntax-meta syntax-selector syntax-css\">elements</span>/1<span class=\"syntax-meta syntax-selector syntax-css\">.1</span>/&quot;,\n);\n\n<span class=\"syntax-meta syntax-selector syntax-css\">#<span class=\"syntax-meta syntax-attribute-selector syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">[</span><span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">derive</span>(<span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">Debug</span>, <span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">Deserialize</span>)<span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">]</span></span>\nstruct Entry </span><span class=\"syntax-meta syntax-property-list syntax-css\"><span class=\"syntax-punctuation syntax-section syntax-property-list syntax-css\">{</span>\n    #[<span class=\"syntax-meta syntax-property-name syntax-css\">deser</span>(<span class=\"syntax-meta syntax-property-name syntax-css\">rename</span> = <span class=\"syntax-meta syntax-property-name syntax-css\">atom</span>!(&quot;<span class=\"syntax-meta syntax-property-name syntax-css\">title</span>&quot;))]\n    <span class=\"syntax-meta syntax-property-name syntax-css\">title</span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-css\">:</span><span class=\"syntax-meta syntax-property-value syntax-css\"> </span><span class=\"syntax-meta syntax-property-value syntax-css\">String,\n    #[deser(rename = dc!(<span class=\"syntax-string syntax-quoted syntax-double syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-css\">&quot;</span>creator<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-css\">&quot;</span></span></span>))]\n    <span class=\"syntax-meta syntax-property-name syntax-css\">creators</span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-css\">:</span><span class=\"syntax-meta syntax-property-value syntax-css\"> </span><span class=\"syntax-meta syntax-property-value syntax-css\">Vec&lt;String&gt;,\n    #[deser(rename = atom!(<span class=\"syntax-string syntax-quoted syntax-double syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-css\">&quot;</span>updated<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-css\">&quot;</span></span></span>))]\n    <span class=\"syntax-meta syntax-property-name syntax-css\">updated</span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-css\">:</span><span class=\"syntax-meta syntax-property-value syntax-css\"> </span><span class=\"syntax-meta syntax-property-value syntax-css\">DateTime&lt;Utc&gt;,\n</span></span><span class=\"syntax-punctuation syntax-section syntax-property-list syntax-css\">}</span>\n\n//<span class=\"syntax-meta syntax-selector syntax-css\"> entries we understand, and everything else is kept as it is\n#<span class=\"syntax-meta syntax-attribute-selector syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">[</span><span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">derive</span>(<span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">Debug</span>, <span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">Deserialize</span>)<span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">]</span></span>\n#<span class=\"syntax-meta syntax-attribute-selector syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">[</span><span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">deser</span>(<span class=\"syntax-entity syntax-other syntax-attribute-name syntax-css\">untagged</span>)<span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">]</span></span>\nenum Item </span><span class=\"syntax-meta syntax-property-list syntax-css\"><span class=\"syntax-punctuation syntax-section syntax-property-list syntax-css\">{</span>\n    E<span class=\"syntax-meta syntax-property-name syntax-css\">ntry</span>(E<span class=\"syntax-meta syntax-property-name syntax-css\">ntry</span>),\n    O<span class=\"syntax-meta syntax-property-name syntax-css\">ther</span>(V<span class=\"syntax-meta syntax-property-name syntax-css\">alue</span>),\n</span><span class=\"syntax-punctuation syntax-section syntax-property-list syntax-css\">}</span>\n\n<span class=\"syntax-meta syntax-selector syntax-css\">let item: Item = DeserializerConfig<span class=\"syntax-entity syntax-other syntax-pseudo-element syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">::</span>new</span>(</span>)\n<span class=\"syntax-meta syntax-selector syntax-css\">    <span class=\"syntax-entity syntax-other syntax-attribute-name syntax-class syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">.</span>resolve_namespaces</span>(true</span>)\n<span class=\"syntax-meta syntax-selector syntax-css\">    <span class=\"syntax-entity syntax-other syntax-attribute-name syntax-class syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">.</span>from_str</span>(r#&quot;\n        &lt;entry xmlns=&quot;http:</span>//<span class=\"syntax-meta syntax-selector syntax-css\">www<span class=\"syntax-entity syntax-other syntax-attribute-name syntax-class syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">.</span>w3</span><span class=\"syntax-entity syntax-other syntax-attribute-name syntax-class syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">.</span>org</span></span>/2005/<span class=\"syntax-meta syntax-selector syntax-css\">Atom&quot;\n               xmlns<span class=\"syntax-entity syntax-other syntax-pseudo-class syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">:</span>d</span>=&quot;http:</span>//<span class=\"syntax-meta syntax-selector syntax-css\">purl<span class=\"syntax-entity syntax-other syntax-attribute-name syntax-class syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">.</span>org</span></span>/<span class=\"syntax-meta syntax-selector syntax-css\">dc</span>/<span class=\"syntax-meta syntax-selector syntax-css\">elements</span>/1<span class=\"syntax-meta syntax-selector syntax-css\">.1</span>/&quot;&gt;\n          &lt;<span class=\"syntax-meta syntax-selector syntax-css\"><span class=\"syntax-entity syntax-name syntax-tag syntax-css\">title</span><span class=\"syntax-punctuation syntax-separator syntax-combinator syntax-css\">&gt;</span>Deser&lt;</span>/<span class=\"syntax-meta syntax-selector syntax-css\"><span class=\"syntax-entity syntax-name syntax-tag syntax-css\">title</span><span class=\"syntax-punctuation syntax-separator syntax-combinator syntax-css\">&gt;</span>\n          &lt;d<span class=\"syntax-entity syntax-other syntax-pseudo-class syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">:</span>creator</span><span class=\"syntax-punctuation syntax-separator syntax-combinator syntax-css\">&gt;</span>John&lt;</span>/<span class=\"syntax-meta syntax-selector syntax-css\">d<span class=\"syntax-entity syntax-other syntax-pseudo-class syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">:</span>creator</span><span class=\"syntax-punctuation syntax-separator syntax-combinator syntax-css\">&gt;</span>\n          &lt;updated<span class=\"syntax-punctuation syntax-separator syntax-combinator syntax-css\">&gt;</span>2026-09-29T21:00:00Z&lt;</span>/<span class=\"syntax-meta syntax-selector syntax-css\">updated<span class=\"syntax-punctuation syntax-separator syntax-combinator syntax-css\">&gt;</span>\n          &lt;d<span class=\"syntax-entity syntax-other syntax-pseudo-class syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">:</span>creator</span><span class=\"syntax-punctuation syntax-separator syntax-combinator syntax-css\">&gt;</span>Jane&lt;</span>/<span class=\"syntax-meta syntax-selector syntax-css\">d<span class=\"syntax-entity syntax-other syntax-pseudo-class syntax-css\"><span class=\"syntax-punctuation syntax-definition syntax-entity syntax-css\">:</span>creator</span><span class=\"syntax-punctuation syntax-separator syntax-combinator syntax-css\">&gt;</span>\n        &lt;</span>/<span class=\"syntax-meta syntax-selector syntax-css\">entry<span class=\"syntax-punctuation syntax-separator syntax-combinator syntax-css\">&gt;</span>\n    &quot;#</span>)?;\n</span></span></code></pre>\n<p>XML uses namespaces which means that names need to be matched by their namespace, not by the prefix the document happens to use. Here the document says <code>d:</code> and the type says <code>dc!</code>. <code>atom!(&quot;title&quot;)</code> is just the string <code>{http://www.w3.org/2005/Atom}title</code>, which works because attributes are expressions. The two creators are collected into one <code>Vec</code> even though there is another element between them, and the text of <code>updated</code> goes straight into a <code>chrono</code> datetime. Because the enum is untagged, the entry has to be buffered before a variant is picked, and deser’s buffer keeps both creators. So the result is an <code>Entry</code> with John and Jane.</p>\n<p>quick-xml, the most popular XML crate for Serde, drops the prefixes and ignores namespaces entirely, so a <code>&lt;x:title&gt;</code> from some other namespace is happily accepted as the title of the entry. The split list part though is considerably worse. A plain <code>Entry</code> fails with a duplicate field error for <code>creator</code>, unless you turn on the <code>overlapped-lists</code> feature (which, remember, is a global additive flag that any crate could set). That feature makes quick-xml read ahead to the end of the element and buffer everything in between, without a limit unless you set one.</p>\n<p>But the feature only helps when quick-xml is hooked up to the struct directly and no buffering is taking place. Wrap the struct in the untagged enum and Serde buffers the entry itself. Read from that buffer, <code>Entry</code> sees <code>creator</code> twice and fails again. The fallback is a map, which keeps only the last <code>creator</code>, and there is no error. With or without the feature you get this:</p>\n<pre><code><span class=\"code-snippet\">Other({&quot;creator&quot;: {&quot;$text&quot;: &quot;Jane&quot;}, &quot;title&quot;: {&quot;$text&quot;: &quot;Deser&quot;}, ...})\n</span></code></pre>\n<p>Notice how John is gone.</p>\n<p>Format specific extension types such as TOML datetimes are another case. TOML has them natively, Serde’s data model does not, so the <code>toml</code> crate passes them on as a map with a magic key. In Deser a datetime is an extension value, which formats that know it keep and all others write as a string:</p>\n<pre><code><span class=\"code-snippet\">let value: Value = deser_toml::from_str(&quot;released = 2026-09-29T21:00:00+02:00&quot;)?;\n\ndeser_json::to_string(&amp;value)?;\n// {&quot;released&quot;:&quot;2026-09-29T21:00:00+02:00&quot;}\ndeser_toml::to_string(&amp;value)?;\n// released = 2026-09-29T21:00:00+02:00\n</span></code></pre>\n<p>The same with <code>serde_json::Value</code> gives you <code>{&quot;released&quot;:{&quot;$__toml_private_datetime&quot;:&quot;2026-09-29T21:00:00+02:00&quot;}}</code>, and reading the value into a <code>chrono::DateTime</code> fails outright with <code>invalid type: map, expected an RFC 3339 formatted date and time string</code>.</p>\n<h2>The Cost</h2>\n<p>So now that you know Deser is at least in theory cool, at what cost?</p>\n<p>It is not free. The design relies on dynamic dispatch and on sinks and emitters that live on the heap, and that has considerable runtime overhead. In my own measurements for JSON, Deser reads somewhere between 33% faster and 60% slower than <code>serde_json depending</code> on the data. On average it’s about 10% slower for reading. Writes are between three times as fast and 70% slower and a wash on average. For YAML and TOML it’s noticeably faster than the Serde based crates, but that is more about the format implementations than the architecture.</p>\n<p>Compile times slightly are better, but not dramatically so. Because it doesn’t monomorphize everything, release builds of derived code are about 2.3 times as fast as with Serde and that get a tiny bit better in practice for your own code as less recompilation is necessary.</p>\n<p>To make Deser’s design work at all, it also uses <code>unsafe</code> internally. Most of this is to keep the chain of borrowed sinks on the heap. I feel like this is fine in the days of Miri and agents, but I know it makes some folks uneasy.</p>\n<p>And well, the biggest cost is that it’s just not Serde.</p>\n<h2>How Much Is There?</h2>\n<p>Quite a lot actually which might be surprising. In addition to the core there is support for <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-derive\">derive</a>.</p>\n<p>It supports all flavorts of JSON you can think of: <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-json\">JSON</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-jsonc\">JSONC</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-json5\">JSON5</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-hj\">HJSON</a>. (Fun fact here: they are all generated out of <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-template-json\">one shared parser template</a>) For binary handling it supports <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-cbor\">CBOR</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-msgpack\">MessagePack</a>. Additionally it does <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-yaml\">YAML</a> 1.1 and 1.2, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-toml\">TOML</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-xml\">XML</a> and all three flavors of Apple’s <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-plist\">plist</a> as well as <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-csv\">CSV/TSV</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-urlencoded\">urlencoded data</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-env\">environment variables</a>. For more crazy contraptions you can <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-path\">attach path info</a> or <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-location\">capture location data</a> as well as support for <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-debug\">debug printing</a>. You can perform <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-validate\">validation</a> as you parse, opt into different <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-encoding\">binary encodings</a> in addition to base64, you can <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-serde\">bridge to serde</a> or capture <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-value\">dynamic values</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-transcode\">transcode</a> between formats or hook it up with <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/deser-tokio\">tokio</a>.</p>\n<p>For documentation see <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://docs.rs/deser/latest/deser/\">docs.rs/deser</a> and the code itself is <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser\">on GitHub</a> alongside <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/mitsuhiko/deser/tree/main/examples\">many examples</a>.</p>\n<p>This entry was tagged <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://lucumr.pocoo.org/tags/rust/\">rust</a></p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://lucumr.pocoo.org/2026/9/29/deser.md\">copy as</a> / <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://lucumr.pocoo.org/2026/9/29/deser.md\">view</a> markdown</p>\n",
  "content_markdown": "[Serde](https://serde.rs/) is an amazing serialization library for Rust and it has been a huge reason why I felt productive with it for years. However already while at Sentry I got quite frustrated with some of the limitations with it but actually replacing Serde is tricky because of the might that it has in the ecosystem. Also because it’s quite hard to actually do better without also making some potentially painful compromises.\n\nHere are three examples of Serde corner cases that show poor interactions of Serde features or unexpected limitations:\n\nA number that is a map\n\nAn internally tagged enum, with `serde_json`‘s `arbitrary_precision` feature turned on:\n\n```\n#[derive(Deserialize)]\n#[serde(tag = \"type\")]\nenum Shape {\n    Circle { radius: f64 },\n}\n\nserde_json::from_str::<Shape>(r#\"{\"type\": \"Circle\", \"radius\": 1.5}\"#)\n// error: invalid type: map, expected f64\n```\n\nSerde’s data model has no place for arbitrary precision numbers, so `serde_json` uses in-band signalling with a map with a magic key. The enum has to buffer the fields until it has seen the tag, and the buffer does not know about the magic key. Because Cargo features are unified, it’s enough for any crate in your dependency graph to turn the feature on.\n\nFlattening breaks integer keys\n\n```\n#[derive(Deserialize)]\nstruct Stats {\n    scores: HashMap<u32, u32>,\n}\n\n#[derive(Deserialize)]\nstruct Report {\n    name: String,\n    #[serde(flatten)]\n    stats: Stats,\n}\n\nserde_json::from_str::<Report>(r#\"{\"name\": \"x\", \"scores\": {\"42\": 23}}\"#)\n// error: invalid type: string \"42\", expected u32 at line 1 column 35\n```\n\n`Stats` on its own parses `{\"scores\": {\"42\": 23}}` just fine. JSON keys are always strings, and `serde_json` only turns them into integers if the type asks for one. However once `flatten` buffers the value, `\"42\"` is just a string. The error also points at the end of the document rather than at the key.\n\nAdapters do not compose\n\n```\nfn from_hex<'de, D: Deserializer<'de>>(d: D) -> Result<u32, D::Error> { ... }\n\n#[derive(Deserialize)]\nstruct Theme {\n    #[serde(deserialize_with = \"from_hex\")]\n    primary: u32,\n    #[serde(deserialize_with = \"from_hex\")]\n    accent: Option<u32>,\n}\n\n//error[E0308]: `?` operator has incompatible types\n//  |\n//  |     #[serde(deserialize_with = \"from_hex\")]\n//  |                                ^^^^^^^^^^ expected `Option<u32>`, found `u32`\n//  |\n//help: try wrapping the expression in `Some`\n//  |\n//  |     #[serde(deserialize_with = Some(\"from_hex\"))]\n//  |                                +++++          +\n```\n\nA function cannot be passed as a type parameter, so there is no way to apply `from_hex` to the inside of an `Option`, a `Vec` or a map. You write another function for every wrapper, and once you have `from_opt_hex` the field is no longer optional unless you also remember to add `#[serde(default)]`.\n\nNone of these are bugs that are easy to fix in Serde. They fall out of its design, and that design is protected by Serde’s stability guarantees.\n\nBack in 2022 I started an experiment called [Deser](https://github.com/mitsuhiko/deser). It’s a serialization library for Rust that takes the user experience of [Serde](https://serde.rs/) and puts it on top of a completely different architecture inspired by [miniserde](https://github.com/dtolnay/miniserde). I never really finished it and it sat around for a few years. I picked it back up, and it has now reached a point where I think it’s worth looking at. Even just to inspire others to see if they want to explore the space.\n\n## The Name And Idea\n\nThe name is Serde with its two halves swapped. Deser is Serde but the other way around. In Serde, a type drives the deserialization process: a `Deserialize` impl asks the deserializer for the kind of value it expects, the format calls back into a visitor. Every nested value is handled by recursion which makes Serde deserialization inherently grow the stack with each level of nesting.\n\nDeser on the other hand turns this around and the format tells the type of the next value and pushes events into a sink. When a sink hits the start of a nested value, it doesn’t call into it but hands back a new sink to a driver, which keeps all state on the heap (in fact, in an arena). On the way out, emitters return their nested values instead of recursing into them.\n\nThat also means that Deser cannot support formats like protobuf that are not self describing. They are in fact quite intentionally left out of the design entirely. Which is one way to say: if you want to “fix” Serde, you need to make some other compromises.\n\nMost of the reasons for Deser’s ideas go back to [Sentry Relay](https://github.com/getsentry/relay), which processes enormous amounts of untrusted JSON. Over the years when I was at Sentry we ran into the same set of problems again and again, and many of them are not really bugs in Serde but consequences of its design. Serde’s stability guarantees mean that a lot of them cannot be fixed without breaking every format and every hand written implementation. Most of these problems come from three decisions:\n\n1. **One set of traits for all formats.** Serde serves both self describing formats (JSON, YAML, TOML, …) and formats where the reader has to know the type upfront (postcard, bincode, protobuf, …). That is incredibly useful, but it means that some features only work with some formats, and you find out at runtime. In case of Serde it also has some odd wrinkles where a derived struct quietly accepts an array in place of an object in JSON for instance.\n\n2. **A fixed data model that loses information when buffering.** Internally tagged enums, untagged enums and `flatten` need to buffer values before they know what to do with them. The buffer can’t hold everything the format knew, errors lose their location and extensions to the ecosystem rely on in-band signalling to express things such as arbitrary precision numbers.\n\n3. **Recursion on the call stack.** Every level of nesting uses stack space. Formats protect against this with a recursion limit, but the moment you go through a code path that doesn’t have one (writing, dynamic values), deeply nested data can take down your process. It also means that a deserialization cannot be paused while you wait for more input.\n\nMany of the corresponding Serde issues have been open for years, and I wrote about [abusing Serde](https://lucumr.pocoo.org/2021/11/14/abusing-serde/) before. People have tried different angles on this over the years. Some went minimal and dropped most features to get fast compiles and no recursion. dtolnay’s own [miniserde](https://github.com/dtolnay/miniserde) is the best example of that, and deser’s trait design was originally modelled after it. Other recent attempts went for runtime reflection, or for a new data model with a focus on binary formats.\n\nIf you want to read up on all of the collected challenges with Serde’s design, I maintain [a lengthy list here](https://github.com/mitsuhiko/deser/blob/main/SERDE.md).\n\n## Dethroning Serde\n\nFirst of all I don’t think it’s likely that one can replace Serde. [The orphan rule](https://smallcultfollowing.com/babysteps/blog/2022/04/17/coherence-and-crate-level-where-clauses/) entrenches Serde incredibly well in the ecosystem. But some things are within the reach of a crate author’s control. In case of Deser it’s completeness.\n\nDeser today implements all important self describing formats from YAML, JSON, TOML, CBOR, JSON5 and the likes, but also XML and plist to really close the gap. XML in particular is something Serde has declined to support, and it shows (more on that below). At the very least format support should not be the reason not to use Deser.\n\nThe second problem usually is that actually solving Serde’s issues comes at a significant cost in compile time and/or runtime performance. Deser is no different. While Deser’s compile times are a bit better than Serde’s, the binary bloat is quite a bit worse and the runtime performance is mixed. It’s roughly comparable if you look at the numbers but depending on the format structure you are losing significantly from some of the tradeoffs.\n\nThat said, it’s now in a state where it’s at least in principle a drop-in replacement where the tradeoffs might work well for users.\n\n## Deser’s Design\n\nDeser does not try to be significantly different than Serde on the surface level. For most uses you derive `Serialize` and `Deserialize` and then start using it with your format implementing crate of choice. Most attributes are very similar, though they are taking Rust expressions instead of strings.\n\n```\nuse deser::{Serialize, Deserialize};\n\n#[derive(Debug, Serialize, Deserialize)]\n#[deser(rename_all = \"camelCase\")]\npub struct Account {\n    id: u64,\n    account_holder: String,\n    #[deser(default)]\n    is_deactivated: bool,\n}\n\nlet account: Account = deser_json::from_str(json)?;\n```\n\nThe difference in the design would become more apparent if you implement a serializer or deserializer yourself. Instead of visitors that call into each other recursively, deserializing a type creates a *sink* which receives events that are directly emitted by the parser, and *serializing produces emitters* that hand out values. Nested sinks and emitters are handed back to a driver, which keeps them on the heap. This design, which is entirely stolen from miniserde, gives some interesting consequences:\n\n- **No stack overflows.** You can arbitrarily nest structures without issues. For untrusted input you set limits with a layer, and you pick the number that you are comfortable with, which is independent of your stack space.\n- **Suspendable.** Because the state lives in the driver, a deserialization can be fed input as it arrives. It’s also `Send`, so it can move between threads while you wait on IO which makes it much nicer to use with tokio. Formats like JSON, CBOR and MessagePack can be parsed as a stream if you so desire.\n- **An extensible data model.** The core data model is small and made of atoms, maps and sequences. For all else, there are extension values (`DateTime`, `Uuid`, etc.) that also all carry a fallback for formats that don’t understand them. Unlike Serde this means it does not rely on in-band signalling of objects with magic keys to smuggle values through.\n- **Lossless buffering.** When a value has to be buffered (for instance because the tag of an internally tagged enum comes last), Deser records the events together with everything the format knew about them. Protocol specific extension types or error locations all survive.\n- **Layers** are a middleware system that sit between the format and your types and can track things like paths, enforce safety limits, rename keys or redact values without having to touch specific code paths.\n- **Native flattening** that doesn’t buffer at all.\n\nOn top of that are a lot of things that I just wanted to have:\n\n- Allow enum tags to be of any type, not just strings\n- Adapters that compose (`as = Option<Vec<DisplayFromStr>>`)\n- Enabling validation as an adapter\n- derive attributes that are real Rust expressions instead of strings\n- bytes as a core functionality in the data model\n- duplicate keys rejected by default and errors that point at the problem\n\nHere is a small configuration type that shows a few of these together:\n\n```\nuse deser::adapters::DisplayFromStr;\nuse deser::de::Recording;\nuse deser::{Deserialize, Serialize};\nuse deser_encoding::Hex;\nuse deser_validate::{Check, NonEmpty, Range};\nuse ipnet::IpNet;\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct Config {\n    // at least one 256-bit key, each written as hex\n    #[deser(as = Check<NonEmpty, Vec<Hex>>)]\n    secret_keys: Vec<[u8; 32]>,\n    // `IpNet` knows nothing about deser, but has `FromStr` and `Display`\n    #[deser(as = Option<Vec<DisplayFromStr>>)]\n    allowed_networks: Option<Vec<IpNet>>,\n    listeners: Vec<Listener>,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\n#[deser(tag = \"type\", rename_all = \"snake_case\")]\npub enum Listener {\n    Unix { path: PathBuf },\n    Tcp {\n        host: IpAddr,\n        #[deser(as = Check<Range<1, 65535>>)]\n        port: u16,\n    },\n    // types this version does not know are kept and written back\n    #[deser(other)]\n    Other(#[deser(tag)] String, Recording),\n}\n```\n\nAdapters are types, so `Hex` can go inside a `Vec`, and `DisplayFromStr` inside a `Vec` inside an `Option`. Validators are adapters too, so `Check<NonEmpty, Vec<Hex>>` decodes the keys and then checks that there is at least one. The catch-all variant keeps the tag and a recording of everything else in case someone wants to process it later.\n\nErrors are something I care a lot about, so here is what happens when a value is wrong:\n\n```\nsecret_keys = [\"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\"]\nallowed_networks = [\"10.0.0.0/8\", \"fd00::/8\"]\n\n[[listeners]]\ntype = \"unix\"\npath = \"/run/app.sock\"\n\n[[listeners]]\nhost = \"127.0.0.1\"\nport = 0\ntype = \"tcp\"\n\n[[listeners]]\ntype = \"quic\"\nhost = \"::1\"\nalpn = [\"h3\"]\n```\n\n```\nlet config: Config = deser_toml::Deserializer::from_str(input)\n    .deserialize_with(|driver| driver.push_layer(PathLayer::new()))?;\n```\n\nNote that here the tag of the internally tagged enum comes last which means that the values have to be buffered until the tag is known. In Serde this is tricky and we would lose the location if we used some tricks to add it. With Deser however, with the path layer enabled Deser you where in the structure the problem is:\n\n```\nUnexpected: invalid value: must be between 1 and 65535 at line 10 column 8 (path: listeners[1].port)\n```\n\n## Deser Meta Data\n\nDeser really wants to be extensible, and XML is a more extreme example of the differences between Deser and Serde. Here is an Atom entry that mixes in Dublin Core for the authors:\n\n```\nuse chrono::{DateTime, Utc};\nuse deser::Deserialize;\nuse deser_value::Value;\nuse deser_xml::DeserializerConfig;\n\ndeser_xml::namespace!(\n    atom = \"http://www.w3.org/2005/Atom\",\n    dc = \"http://purl.org/dc/elements/1.1/\",\n);\n\n#[derive(Debug, Deserialize)]\nstruct Entry {\n    #[deser(rename = atom!(\"title\"))]\n    title: String,\n    #[deser(rename = dc!(\"creator\"))]\n    creators: Vec<String>,\n    #[deser(rename = atom!(\"updated\"))]\n    updated: DateTime<Utc>,\n}\n\n// entries we understand, and everything else is kept as it is\n#[derive(Debug, Deserialize)]\n#[deser(untagged)]\nenum Item {\n    Entry(Entry),\n    Other(Value),\n}\n\nlet item: Item = DeserializerConfig::new()\n    .resolve_namespaces(true)\n    .from_str(r#\"\n        <entry xmlns=\"http://www.w3.org/2005/Atom\"\n               xmlns:d=\"http://purl.org/dc/elements/1.1/\">\n          <title>Deser</title>\n          <d:creator>John</d:creator>\n          <updated>2026-09-29T21:00:00Z</updated>\n          <d:creator>Jane</d:creator>\n        </entry>\n    \"#)?;\n```\n\nXML uses namespaces which means that names need to be matched by their namespace, not by the prefix the document happens to use. Here the document says `d:` and the type says `dc!`. `atom!(\"title\")` is just the string `{http://www.w3.org/2005/Atom}title`, which works because attributes are expressions. The two creators are collected into one `Vec` even though there is another element between them, and the text of `updated` goes straight into a `chrono` datetime. Because the enum is untagged, the entry has to be buffered before a variant is picked, and deser’s buffer keeps both creators. So the result is an `Entry` with John and Jane.\n\nquick-xml, the most popular XML crate for Serde, drops the prefixes and ignores namespaces entirely, so a `<x:title>` from some other namespace is happily accepted as the title of the entry. The split list part though is considerably worse. A plain `Entry` fails with a duplicate field error for `creator`, unless you turn on the `overlapped-lists` feature (which, remember, is a global additive flag that any crate could set). That feature makes quick-xml read ahead to the end of the element and buffer everything in between, without a limit unless you set one.\n\nBut the feature only helps when quick-xml is hooked up to the struct directly and no buffering is taking place. Wrap the struct in the untagged enum and Serde buffers the entry itself. Read from that buffer, `Entry` sees `creator` twice and fails again. The fallback is a map, which keeps only the last `creator`, and there is no error. With or without the feature you get this:\n\n```\nOther({\"creator\": {\"$text\": \"Jane\"}, \"title\": {\"$text\": \"Deser\"}, ...})\n```\n\nNotice how John is gone.\n\nFormat specific extension types such as TOML datetimes are another case. TOML has them natively, Serde’s data model does not, so the `toml` crate passes them on as a map with a magic key. In Deser a datetime is an extension value, which formats that know it keep and all others write as a string:\n\n```\nlet value: Value = deser_toml::from_str(\"released = 2026-09-29T21:00:00+02:00\")?;\n\ndeser_json::to_string(&value)?;\n// {\"released\":\"2026-09-29T21:00:00+02:00\"}\ndeser_toml::to_string(&value)?;\n// released = 2026-09-29T21:00:00+02:00\n```\n\nThe same with `serde_json::Value` gives you `{\"released\":{\"$__toml_private_datetime\":\"2026-09-29T21:00:00+02:00\"}}`, and reading the value into a `chrono::DateTime` fails outright with `invalid type: map, expected an RFC 3339 formatted date and time string`.\n\n## The Cost\n\nSo now that you know Deser is at least in theory cool, at what cost?\n\nIt is not free. The design relies on dynamic dispatch and on sinks and emitters that live on the heap, and that has considerable runtime overhead. In my own measurements for JSON, Deser reads somewhere between 33% faster and 60% slower than `serde_json depending` on the data. On average it’s about 10% slower for reading. Writes are between three times as fast and 70% slower and a wash on average. For YAML and TOML it’s noticeably faster than the Serde based crates, but that is more about the format implementations than the architecture.\n\nCompile times slightly are better, but not dramatically so. Because it doesn’t monomorphize everything, release builds of derived code are about 2.3 times as fast as with Serde and that get a tiny bit better in practice for your own code as less recompilation is necessary.\n\nTo make Deser’s design work at all, it also uses `unsafe` internally. Most of this is to keep the chain of borrowed sinks on the heap. I feel like this is fine in the days of Miri and agents, but I know it makes some folks uneasy.\n\nAnd well, the biggest cost is that it’s just not Serde.\n\n## How Much Is There?\n\nQuite a lot actually which might be surprising. In addition to the core there is support for [derive](https://github.com/mitsuhiko/deser/tree/main/deser-derive).\n\nIt supports all flavorts of JSON you can think of: [JSON](https://github.com/mitsuhiko/deser/tree/main/deser-json), [JSONC](https://github.com/mitsuhiko/deser/tree/main/deser-jsonc), [JSON5](https://github.com/mitsuhiko/deser/tree/main/deser-json5) and [HJSON](https://github.com/mitsuhiko/deser/tree/main/deser-hj). (Fun fact here: they are all generated out of [one shared parser template](https://github.com/mitsuhiko/deser/tree/main/deser-template-json)) For binary handling it supports [CBOR](https://github.com/mitsuhiko/deser/tree/main/deser-cbor) and [MessagePack](https://github.com/mitsuhiko/deser/tree/main/deser-msgpack). Additionally it does [YAML](https://github.com/mitsuhiko/deser/tree/main/deser-yaml) 1.1 and 1.2, [TOML](https://github.com/mitsuhiko/deser/tree/main/deser-toml), [XML](https://github.com/mitsuhiko/deser/tree/main/deser-xml) and all three flavors of Apple’s [plist](https://github.com/mitsuhiko/deser/tree/main/deser-plist) as well as [CSV/TSV](https://github.com/mitsuhiko/deser/tree/main/deser-csv), [urlencoded data](https://github.com/mitsuhiko/deser/tree/main/deser-urlencoded) and [environment variables](https://github.com/mitsuhiko/deser/tree/main/deser-env). For more crazy contraptions you can [attach path info](https://github.com/mitsuhiko/deser/tree/main/deser-path) or [capture location data](https://github.com/mitsuhiko/deser/tree/main/deser-location) as well as support for [debug printing](https://github.com/mitsuhiko/deser/tree/main/deser-debug). You can perform [validation](https://github.com/mitsuhiko/deser/tree/main/deser-validate) as you parse, opt into different [binary encodings](https://github.com/mitsuhiko/deser/tree/main/deser-encoding) in addition to base64, you can [bridge to serde](https://github.com/mitsuhiko/deser/tree/main/deser-serde) or capture [dynamic values](https://github.com/mitsuhiko/deser/tree/main/deser-value), [transcode](https://github.com/mitsuhiko/deser/tree/main/deser-transcode) between formats or hook it up with [tokio](https://github.com/mitsuhiko/deser/tree/main/deser-tokio).\n\nFor documentation see [docs.rs/deser](https://docs.rs/deser/latest/deser/) and the code itself is [on GitHub](https://github.com/mitsuhiko/deser) alongside [many examples](https://github.com/mitsuhiko/deser/tree/main/examples).\n\nThis entry was tagged [rust](https://lucumr.pocoo.org/tags/rust/)\n\n[copy as](https://lucumr.pocoo.org/2026/9/29/deser.md) / [view](https://lucumr.pocoo.org/2026/9/29/deser.md) markdown\n",
  "dateCreated": "2026-09-29T23:14:03.129222144+00:00",
  "date_created": "2026-09-29T00:00:00+00:00",
  "date_modified": "2026-09-29T00:00:00+00:00",
  "date_published": "2026-09-29T00:00:00+00:00",
  "description": "Serde is an amazing serialization library for Rust and it has been a huge reason why I felt productive with it for years. However already while at Sentry I got quite frustrated with some of the limitations with it but actually replacing…",
  "external_url": "https://lucumr.pocoo.org/2026/9/29/deser/",
  "git": {
    "commit": "ebb0941c5c06a2ab2377342d607535262a3e969e",
    "path": "items/lucumr-pocoo-org/2026/09/2026-09-29-deser-rethinking-rust-serialization.md",
    "permalink": "https://github.com/aymericbeaumet/aggr-instance/blob/ebb0941c5c06a2ab2377342d607535262a3e969e/items/lucumr-pocoo-org/2026/09/2026-09-29-deser-rethinking-rust-serialization.md"
  },
  "id": "urn:aggr:item:ce556d3b01c17d0bb01238d79a0adba4ebf9e626",
  "isBasedOn": {
    "@id": "https://lucumr.pocoo.org/2026/9/29/deser/"
  },
  "mainEntity": {
    "@id": "https://lucumr.pocoo.org/2026/9/29/deser/",
    "@type": "CreativeWork",
    "archivedAt": {
      "@id": "https://aggr.aymericbeaumet.com/items/lucumr-pocoo-org/2026-09-29-deser-rethinking-rust-serialization/#webpage"
    },
    "author": [
      {
        "@type": "Person",
        "name": "Armin Ronacher"
      }
    ],
    "dateModified": "2026-09-29T00:00:00+00:00",
    "datePublished": "2026-09-29T00:00:00+00:00",
    "headline": "Deser: Rethinking Rust Serialization",
    "timeRequired": "PT14M",
    "url": "https://lucumr.pocoo.org/2026/9/29/deser/",
    "wordCount": 3003
  },
  "name": "Archived snapshot: Deser: Rethinking Rust Serialization",
  "preview": null,
  "reading_minutes": 14,
  "replicated_at": null,
  "source": "lucumr-pocoo-org",
  "source_name": "Armin Ronacher's Thoughts and Writings",
  "summary": null,
  "tags": [
    "rust"
  ],
  "temporalCoverage": "2026-09-29T23:14:03.129222144+00:00",
  "title": "Deser: Rethinking Rust Serialization",
  "url": "https://aggr.aymericbeaumet.com/items/lucumr-pocoo-org/2026-09-29-deser-rethinking-rust-serialization/",
  "word_count": 3003
}