# serde_json: strongly typed JSON for Rust, and when untyped Value is the right call

> serde_json is the JSON crate most Rust projects reach for, wrapping serde's Serialize and Deserialize traits around a parser. It is excellent when the shape of your data is known, and awkward when it is not.

**serde-rs/json** — Strongly typed JSON library for Rust

- Repository: https://github.com/serde-rs/json
- Stars: 5,638 · Forks: 675
- Language: Rust
- License: Apache-2.0
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/serde-rs-json

## What serde_json is for, and who ends up using it

The crate exists to move data between three representations: raw JSON text, an untyped tree, and a strongly typed Rust struct. The README frames the choice that way, and it is the most useful thing in the document. If you receive a payload on an HTTP endpoint and you know its fields, you want the typed path. If you are inspecting a payload whose shape you do not control, you want the untyped path. If you are just relaying bytes, you may not need the crate at all.

The audience is Rust developers writing services, CLIs or tools that speak to APIs returning JSON. Because serde_json sits on top of serde, the typed path works for any type implementing Deserialize, including standard library types such as Vec<T> and HashMap<K, V> and any struct annotated with #[derive(Deserialize)]. That derivation is what makes the crate feel like part of the language rather than a library bolted on.

## The Value enum and the typed struct are two different parsers

The README presents serde_json::Value as a recursive enum with Null, Bool, Number, String, Array and Object variants. Any valid JSON document can be held in that shape, and from_str, from_slice and from_reader all produce it when you ask for it. Indexing a Value with square brackets returns a &Value, and the failure mode is quiet: a wrong type, a missing map key or an out-of-bounds array index all yield Value::Null rather than an error. That is a deliberate design choice and it is the reason the README warns about typos like v["nmae"].

The typed path uses the same from_str function with a different target type. Assign the result to a Person and serde interprets the input against that struct, producing what the README calls informative error messages when the layout does not conform. The compiler then helps you everywhere the value is used. The trade-off is real: Value is flexible and unhelpful, structs are rigid and helpful. Picking the wrong one for a given call site is the most common mistake with this crate.

## Installing serde_json and parsing a first payload

The README gives the dependency line directly. Add it to your Cargo.toml and the default feature set, which is std, comes along.

```toml
[dependencies]
serde_json = "1.0"
```

For the typed path you also need serde with the derive feature, because the Serialize and Deserialize traits live there. The README's own example imports them from serde.

```toml
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
```

A minimal program defines a struct with #[derive(Serialize, Deserialize)] and parses a string into it. The README's typed example uses a Person with a name, an age and a vector of phone strings; the same from_str call that would have produced a Value produces a Person instead.

```rust
use serde::{Deserialize, Serialize};
use serde_json::Result;

#[derive(Serialize, Deserialize)]
struct Person {
    name: String,
    age: u8,
    phones: Vec<String>,
}

fn typed_example() -> Result<()> {
    let data = r#"{"name":"John Doe","age":43,"phones":["+44 1234567"]}"#;
    let p: Person = serde_json::from_str(data)?;
    println!("Please call {} at the number {}", p.name, p.phones[0]);
    Ok(())
}
```

What you should see is the printed line with the name and first phone number. Note the age field is a u8, so a document containing an age above 255 fails to deserialize rather than silently truncating, which is the behaviour you want from a typed parser. If you instead want the untyped path, the README's other example parses into a Value and indexes it with v["name"] and v["phones"][0]; the printed output keeps the JSON quotation marks around those strings because the indexed type is &Value, not &str.

## Where serde_json is the wrong tool

The untyped Value path will not catch a misspelled key. The README states this plainly, and it is not a bug you can configure away: indexing returns Value::Null for a missing key, so a typo propagates as a null that only fails later, somewhere else. If your code has dozens of v["field"] accesses, the compiler cannot help with any of them. The README's own advice is to use the typed path instead once the work stops being trivial.

The second limitation is structural. serde_json deserializes into a type you already know. It is not a schema validator. It will not tell you that a document conforms to a JSON Schema, and it will not collect every violation in a malformed document; it stops at the first error the deserializer hits for the target type. If your job is validating arbitrary third-party payloads against a published schema, this crate is the wrong layer.

Third, the error messages are informative but they are tied to the target type. Change the struct and the errors change. That is fine when you own both ends and awkward when the payload comes from a party you cannot negotiate with.

## serde_json versus serde-json-core and the question of std

The related searches turn up serde-json-core, and the distinction is worth stating because the names are close. serde_json is built around the standard library by default: the default feature is std, and the Cargo.toml shows std enabling memchr/std and serde_core/std. There is also an alloc feature for heap-allocated collections without the rest of the standard library, and the Cargo.toml carries a note that disabling both std and alloc is not supported yet. That note is the boundary.

serde-json-core targets the no_std, no-alloc end of the spectrum, which is where embedded and bare-metal work lives. The difference in approach is not cosmetic: without an allocator you cannot build a serde_json::Value tree at all, because Value's Object and Array variants hold owned collections. So the choice between the two crates is really a question about whether you have an allocator, not about which API you prefer. If you are on a microcontroller with no heap, serde_json is not the tool regardless of how familiar its API is.

## Maintenance, features and what the licence actually says

The repository is not archived, and the last push was on 2026-08-08. The most recent release listed is v1.0.151 from 2026-07-20, preceded by v1.0.150 in May 2026 and v1.0.149 in January 2026. The version numbers sit at 1.0.x, which is the stability signal that matters here: a 1.0 line with patch releases means the API surface is not expected to churn, and the upgrade cost is normally a version bump in Cargo.toml. The Cargo.toml declares rust-version = 1.71, so that is the floor to check against your toolchain before you pin.

Features are opt-in and worth reading before you enable them. The docs.rs metadata lists preserve_order, raw_value and unbounded_depth, and the playground metadata lists float_roundtrip, raw_value and unbounded_depth. preserve_order pulls in the optional indexmap dependency, which changes how object keys are stored, so it is a decision about ordering semantics, not a free flag. unbounded_depth is exactly what the name suggests and deserves the same suspicion you would give any unbounded setting on input you do not control.

On licensing: Cargo.toml declares MIT OR Apache-2.0, and the repository root contains LICENSE-MIT and LICENSE-APACHE. The GitHub metadata lists Apache-2.0, which is one half of the dual grant rather than the whole picture. If your organisation has a policy about which of the two it accepts, read both files in the repository rather than the repository label. That is a description of what the files say, not legal advice.

## Conclusion

Adopt serde_json when your JSON has a known shape and you want the compiler to police it, or when you only need to inspect a payload before forwarding it. Do not adopt it as a schema validator for arbitrary third-party documents, and do not expect the untyped Value path to catch typos. Before committing, check the Cargo.toml feature list for the version you pin, and confirm that the MSRV of 1.71 fits your toolchain. The repository's last push was on 2026-08-08 and the newest release is v1.0.151.

## FAQ

### How does serde_json work?

It converts between JSON text, an untyped serde_json::Value tree, and strongly typed Rust structures. Functions such as from_str, from_slice and from_reader parse input, and the target type you assign the result to decides which representation you get.

### What is serde_json used for?

The README describes three common uses: handling JSON as raw text, handling it as an untyped or loosely typed representation via serde_json::Value, and mapping it into strongly typed Rust data structures through serde's Deserialize trait.

### What is the purpose of serde_json in Rust?

It provides the JSON implementation for serde, so types annotated with #[derive(Serialize, Deserialize)] can be written to and read from JSON. The README notes it works with built-in types like Vec<T> and HashMap<K, V> as well as your own structs and enums.

### What does serializing JSON mean in serde_json?

Serde is a framework for serializing and deserializing Rust data structures, and JSON is one format it targets. Serializing means turning a Rust value into JSON text; the README shows the reverse direction with from_str producing a Person from a JSON string.

## Sources

- [Issues](https://github.com/serde-rs/json/issues)
- [License: Apache-2.0](https://github.com/serde-rs/json/blob/master/LICENSE)
- [README](https://github.com/serde-rs/json/blob/master/README.md)
- [Releases](https://github.com/serde-rs/json/releases)
- [serde-rs/json on GitHub](https://github.com/serde-rs/json)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/serde-rs-json
