MicroJSON

This post first appeared 24 October 2021.

I published microjson today.

It’s a tiny crate, designed to do exactly one thing — read JSON. It’s not very fast, doesn’t let you serialize objects, doesn’t unpack JSON into custom structs1 and doesn’t expose multiple APIs.

But there is one other thing it doesn’t do which I think is important: it doesn’t allocate any memory.

It also doesn’t have any dependencies.

No allocations: no_std

The use case for microjson is tiny applications (embedded) that need to extract a small amount of data from some JSON once. You read in the bytes of the JSON and you run through the data. Random access to arrays is discouraged — instead, iterate through the array2.

This means that we don’t ever need to allocate anything on the heap. And if we don’t need to allocate anything, we shouldn’t allocate anything. microjson tries to do this. We barely allocate anything on the stack, and all “data” is actually pointers to positions in the input string.

At the moment, Rust’s support for switching out/omitting global (or *shudder* non-global) allocators is… sketchy. The easiest way to enforce that we don’t need an allocator is to make the whole crate not depend on the standard library.

It turns out that microjson doesn’t need (or want) most of what is in the standard library and can survive off the core library. For this reason, microjson is no_std. This is great because it opens up the possibility of using the crate in situations where you can’t have a standard library. Don’t fear though, it can still be used in a std project!

No dependencies

Rust has a fantastic package manager, crates.io, on which microjson is published. It makes including dependencies very easy (just add them to your Cargo.toml) and encourages code reuse.

In general these are great things, but can lead to dependency creep. Projects depend on crates that depend on crates that depend on crates and so on. Eventually, one simple project could draw in a lot of completely unrelated crates, ballooning compile time, increasing attack surface area, and increasing code complexity3.

microjson needs no dependencies so you can feel free to include it without worrying that it will bloat your project.

The only dependency in Cargo.toml is

[dev-dependencies]
criterion = "0.3"

But because this is a dev-dependency (and only used for benchmarking) it won’t be included in any projects that use microjson!

Why you should consider microjson for your next project

Here are some use-cases for when you might like to try this crate out:

Some Sample Code

Here’s some brief sample code from the docs.

let input = r#" [0, 1, 2, 3, 4, 5] "#;

let array = JSONValue::parse(input)?;

for (n, item) in array.iter_array()?.enumerate() {
    let value = item.read_integer();
    assert_eq!(value, Ok(n as isize));
}

Note how we iterate through the array, rather than accessing into it. Objects are similar to use:

let input = r#" { "arr": [3, "foo", 3.625, false] } "#;

let object = JSONValue::parse(input)?;

assert_eq!(
    object.get_key_value("arr")?
      .iter_array()?
      .nth(2).unwrap()
      .read_float(),
    Ok(3.625)
);

Why you should consider something else for your next project

On the other hand, there are plenty of other options out there.

Perhaps you know the format of your incoming JSON exactly and want it de-serialised straight to a struct. Perhaps you need to generate JSON. Or perhaps you want blinding speed and a full suite of tests that will make your eyes water.

Here is (a very non-exhaustive) list of options:

Its also a personal project and there is a lot of scope for improvement. If you need production-ready, bulletproof libraries, you may want to look elsewhere.


I’d appreciate feedback, both on microjson itself and if I’ve maligned/omitted any other JSON parsing crates. I haven’t had time to try them all out, as none seemed to tick all the boxes I needed for my project exactly. Please let me know if you have any comments, and as always, feel free to create issues and pull requests on GitHub.


  1. Like serde↩︎

  2. On the list of TODOs is to implement iterating through key/value pairs on objects. ↩︎

  3. One example of this was an issue with minifb, a window creation library that among its build dependencies needed clap, an incredibly powerful command line parser. The library itself never touched the command line, but in some compiles, clap took the majority of the compilation time. One feels sure that a more minimalistic parser could have been included with the offending dependency, the ubiquitous bindgen↩︎