Library / SDK
dtolnay/miniserde avatar
dtolnay/miniserde

miniserde: serialization without monomorphization or recursion

Data structure serialization library with several opposite design goals from Serde

955 stars34 forksRustApache-2.0

At a glance

What is it?
miniserde is a Rust library that does the same job as Serde and makes the opposite trade at almost every step. It uses trait objects instead of generic code, it never recurses, it cannot fail while serializing, and it refuses to serialise anything more interesting than a struct with named fields. The author calls it a prototype, and the readme is careful to say what that means.
Who is it for?
miniserde suits a program that needs to read and write JSON quickly with a small binary and a fast compile, and that has no need for error messages, custom attributes or data-carrying enums, because those are exactly the features it trades away. It is a poor fit as a general purpose replacement for Serde, and the readme says so in the plainest terms, that feature requests are practically guaranteed to be rejected and that if your use case is not covered you should use Serde.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 94 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • dtolnay/miniserde955 ★ · +0
Star history
Repository2026-10-212026-11-07
dtolnay/miniserde955955

0 Stars in the last 17 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

A prototype with the opposite goals

The description in the repository is the whole thesis: a data structure serialization library with several opposite design goals from Serde.

And the framing is unusually careful. It is a prototype of such a library. As a prototype it is not a production quality engineering artifact the way Serde is, and at the same time it is more than a proof of concept and should be totally usable for the range of use cases that it targets, which is qualified below.

That qualification is the sentence to hold on to, because it defines the boundary. There is a range of use cases for which this is the right tool, and the readme spends most of its length marking where that range ends.

The library is published on the crate registry and documented on the documentation site, and the dependency is a single line in a manifest.

toml
[dependencies]
miniserde = "0.1"

The example in the readme is a small struct with two fields, derived for both directions, converted to a JSON string and parsed back.

rust
use miniserde::{json, Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Example {
 code: u32,
 message: String,
}

fn main() -> miniserde::Result<()> {

The shape of the example is the shape of the library, which is to say that a plain data struct with named fields and primitive types is the whole supported surface, and the author says so explicitly further down.

The performance claim, stated carefully

The first section of the comparison is titled similarly to Serde, with a sarcastic edge, and the numbers are specific enough to be checked.

The claim is that the library is way faster than it deserves to be, and the author's own framing is that with very little profiling and optimisation so far, and with opportunities for improvement still available, it is on par with one JSON library for some use cases, slower by a factor of one and a half for most, and slower by a factor of two for some.

The remark that this is remarkable comes with the reason attached, and the reason is the other advantages: a library that is a factor of two slower than the fastest option, while also being smaller, simpler and free of recursion, is a different bargain rather than a worse one.

So the performance section is not a claim to win a benchmark. It is a claim that the performance is acceptable while the design gives you something else you cannot get from the fast option, and the readme is honest that the library is not yet tuned.

That combination of an honest multiplier and an untuned implementation is worth reading carefully if you are benchmarking. A factor of one and a half is a number that could move in either direction as the author profiles it, and a number that moved enough to matter would change which tool you should pick for your data.

No monomorphization, and twelve times less code

The two structural differences are the ones that shape everything else.

The first is no monomorphization. There are no nontrivial generic methods, and all serialization and deserialization happens in terms of trait objects, so no code is compiled more than once across different generic parameters. The readme contrasts this with the JSON library in the Serde family, which needs to stamp out a fair amount of generic code for each choice of data structure being handled.

The consequence is stated plainly: the derived implementations compile lightning fast and occupy very little size in the executable. If you have ever waited for a serialisation crate to compile, or watched a binary grow because of code generated for a dozen data shapes, this is the trade that matters.

The second difference is scale. The implementation is less code by a factor of twelve compared with the Serde family, and less code even than the plain JSON crate, which provides no derive macro and cannot manipulate strongly typed data.

A factor of twelve is a specific claim about how much complexity a design can shed when it declines to be general. The readme pairs that with a blunt statement of what it means in practice: this library does not tackle as expansive a range of use cases as Serde does, feature requests are practically guaranteed to be rejected, and if your use case is not already covered you should use Serde.

For a library this small, a policy that rejects feature requests is not a lack of hospitality, it is the only way the design stays intact.

No recursion, and therefore no stack overflow

The third difference is about robustness rather than speed, and it is the one that would justify a switch on its own.

Serde depends on recursion for serialization as well as deserialization. Every level of nesting in the data means more stack usage, until eventually you overflow the stack. Some formats set a cap on nesting depth to prevent stack overflows and simply refuse to deserialize deeply nested data.

In miniserde neither side involves recursion, so you can safely process arbitrarily nested data without exposure to stack overflows. The readme adds a detail that shows the care taken: not even the destructor of the untyped value type is recursive, so you can safely nest those arbitrarily either.

That last point matters more than it sounds. A recursive destructor is a crash waiting for a deeply nested document, and it is the kind of bug that only appears when you are parsing input from a stranger, which is precisely when you are using a deserialiser.

So the design gives you a specific guarantee, that depth is bounded by memory rather than by the stack,

No error messages, and serialization that cannot fail

Two of the differences remove error handling from the library, and the readme argues for them rather than apologising for them.

When deserialization fails, the error type is a unit struct containing no information, and the readme says this is a legitimate strategy and not just laziness. If your use case does not require error messages, you save on compiling and on having your instruction cache polluted by error handling code. If you do need error messages, you can pass the same input to the other library and get a line, a column and a helpful description of the failure, which keeps error handling logic out of the caches along the performance critical path.

That is a genuinely interesting position. The expensive part of a deserialiser is often not the parsing but the machinery for building a good error, and a hot loop that only needs to know whether the input was valid should not pay for the other kind.

The related difference is infallible serialization. Serialization always succeeds, which means the library cannot serialise some types that the other one can, a mutex for instance, which may fail to serialise because of poisoning. And the output is always a string, not something like an input or output stream, which may be fallible.

So the choice is not free, but it is bounded and stated. You give up fallible outputs and error detail, and you get a library that cannot fail in the middle of writing your data.

JSON only, structs only, one attribute

The last three differences are about the surface area, and together they define what this library will never be.

It is JSON only. The readme notes that the same approach could be made to work for other data formats, but that it is not a goal to enable that through what the library exposes.

It handles structs and unit variants only. The derive macros refuse anything other than a braced struct with named fields, or an enum with C-style variants. Tuple structs are not supported, and enums with data in their variants are not supported.

And there is no customisation. The other library has a large number of knobs for configuring the derived behaviour through attributes, and the ultimate option of hand writing implementations of its traits. miniserde provides exactly one attribute, which renames, and severely restricts the kind of on-the-fly manipulation that is possible in custom implementations. The readme's advice is blunt: if you need any of this, use Serde, and it is a great library.

The reason to prefer a library that refuses things is that a refusal is checkable. A derive macro that rejects a tuple struct fails at compile time with a clear message, and a data model that is only structs with named fields is a data model you can serialise anywhere, including in a language with no derive macros at all, which is the implicit argument of the whole design.

The packaging, and what the dependency list reveals

The manifest is small and tells you how the implementation is built.

The package version is 0.1.46, the edition is 2021, and the minimum compiler version is 1.71. The licence is MIT or Apache-2.0 at your option, with the standard note that contributions are dual licensed the same way.

The category list includes no-std, and the features are a default of std and a std flag, so the crate is nominally usable without the standard library. The runtime dependencies are three: an integer to ASCII conversion library, an internal helper crate that is version-locked to the exact release, and a floating point formatting library.

The version lock on the internal helper is the detail to notice. A derive macro and the runtime it generates have to agree exactly, and pinning the helper to the same version number is how a pre-release scheme keeps them in step.

The development dependencies are more revealing. The other serialisation family is present as a dev dependency, and it is used in the tests, which is how a library can claim compatibility with it while not depending on it. There is a compile-fail test framework with diff output, which is how the refusals are tested, since a library whose main feature is saying no needs tests that assert the no. There is an automod helper, an indentation-aware string helper, a version-conditional test helper, and a benchmarking harness that is excluded from the published package.

The workspace has two members, the derive crate and a test crate, so the macro and the runtime are separate packages that version together. The documentation build is configured on the documentation site with the link definitions and the macro expansion enabled, which is what makes a derive macro's documentation readable.

On the release history, versions 0.1.44 and 0.1.45 came out in December 2025 and 0.1.46 in July 2026, with the last push on 2026-08-06 and the repository not archived. So this is a prototype that is still being moved, and the readme's description of it as a prototype is accurate rather than a disclaimer.

Editorial conclusion

miniserde suits a program that needs to read and write JSON quickly with a small binary and a fast compile, and that has no need for error messages, custom attributes or data-carrying enums, because those are exactly the features it trades away. It is a poor fit as a general purpose replacement for Serde, and the readme says so in the plainest terms, that feature requests are practically guaranteed to be rejected and that if your use case is not covered you should use Serde. Before you choose it, check that your data model is only structs with named fields and unit variants, because the derive macro will refuse anything else rather than degrade quietly.

Frequently asked questions

What is miniserde and how does it differ from Serde?

It is a Rust serialisation library with the opposite design goals to Serde. It uses trait objects instead of monomorphization, has no recursion in serialising or deserialising, has no deserialisation error messages, cannot fail while serialising, supports JSON only, and its derive macros handle only braced structs with named fields and enums with unit variants.

Why does miniserde avoid recursion?

Because recursion means every level of nesting consumes more stack until it overflows, and some formats therefore cap nesting depth and refuse deeply nested data. In miniserde neither serialisation nor deserialisation recurses, so arbitrarily nested data can be processed safely, and even the destructor of the untyped value type is not recursive.

What happens when miniserde fails to deserialise?

The error type is a unit struct with no information, which the readme presents as a deliberate strategy: if you do not need error messages you save on compiling and on instruction cache pressure. If you do need them, you can pass the same input to another library to get a line, a column and a description of the failure.

Can I use miniserde for something other than JSON?

Not through the library as it exists. The readme says the same approach could be made to work for other data formats, but that enabling it is not a goal of what the library exposes. It also serialises only to a string, never to a stream, because a stream write can fail.

Official sources

  1. dtolnay/miniserde on GitHub
  2. Issues
  3. License: Apache-2.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dtolnay-miniserde.svg)](https://hysenlabs.com/projects/dtolnay-miniserde)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/dtolnay-miniserde.svg)](https://hysenlabs.com/projects/dtolnay-miniserde)

Was this useful?