Overview
5 min read
In TypeScript, turning data into JSON and back is built into the language — JSON.parse and JSON.stringify are always there. Rust takes a different route: serialization lives in a single, dominant framework called Serde (a contraction of serialize/deserialize). Instead of one hard-coded JSON object, Serde splits the problem into data types (your structs and enums, which implement the Serialize/Deserialize traits) and data formats (JSON, TOML, YAML, MessagePack, …, each a separate crate) that meet through a shared data model. The payoff: you annotate a type once and get every format for free — and unlike JSON.parse, deserialization validates against a real type and returns a Result instead of an unchecked any. This section takes you from that mental model through derive macros, attributes, dynamic JSON, alternative formats, fully hand-written (de)serialization, and performance.
What You’ll Learn
Section titled “What You’ll Learn”- How
JSON.parse/JSON.stringifymap onto Serde’sSerialize/Deserializetraits, and the data-model architecture that lets one type target every format - How to set up
serde(withfeatures = ["derive"]) andserde_json, and round-trip values withto_string/from_str - What
#[derive(Serialize, Deserialize)]actually generates for structs and enums - How structs map to JSON, including nested types,
Vec/HashMap,Optionfields, and the four enum representations (externally/internally/adjacently tagged and untagged) - How to work with dynamic, schema-less JSON via
serde_json::Valueand thejson!macro — and when to prefer typed structs instead - The common Serde attributes —
rename,rename_all,skip,skip_serializing_if,default,flatten,tag,with— and what each controls - How the same derived type serializes to TOML, YAML, MessagePack, bincode, and CSV through the Serde ecosystem
- How to hand-write
Serialize/Deserialize, useserialize_with/deserialize_with, and apply remote derive for types you do not own - How to make serialization fast: borrowing (
&str,#[serde(borrow)]), zero-copy parsing, streaming, avoidingValue, and reusing buffers
Topics
Section titled “Topics”| Topic | Description |
|---|---|
| Serde Intro | JSON.parse/JSON.stringify → Serde; the Serialize/Deserialize traits and the data-model architecture (data types ↔ formats). |
| Serde Basics | Setting up serde (features = ["derive"]) and serde_json; the to_string/from_str round-trip, compile-verified. |
| Deriving Serialize/Deserialize | #[derive(Serialize, Deserialize)] on structs and enums, and what the macros generate. |
| Structs and JSON | Structs ↔ JSON: nested types, Vec/HashMap, Option fields, and the four enum representations. |
| Dynamic JSON | Schema-less JSON with serde_json::Value, the json! macro, indexing, and when to use Value vs typed structs. |
| Serde Attributes | #[serde(rename, rename_all, skip, skip_serializing_if, default, flatten, tag, with)] and the other everyday attributes. |
| Other Formats | The same data in other formats: TOML, YAML (serde_norway), MessagePack (rmp-serde), bincode, and CSV. |
| Custom Serialization | Hand-written Serialize/Deserialize, serialize_with/deserialize_with, and remote derive. |
| Performance | Borrowing (&str, #[serde(borrow)]), zero-copy, streaming, avoiding Value, and buffer reuse. |
Learning Objectives
Section titled “Learning Objectives”By the end of this section, you will be able to:
- Explain how Serde’s data-type/data-format split differs from TypeScript’s single built-in
JSONobject, and why it makes types format-agnostic - Add and configure
serdeandserde_json, and round-trip your own types withto_string,to_string_pretty, andfrom_str - Derive
Serialize/Deserializefor structs and enums and reason about the code the macros emit - Map realistic API payloads — nested objects, collections, optional fields, and discriminated unions — to Rust types and back
- Manipulate untyped JSON with
Valueand thejson!macro, and decide deliberately when typed structs are the better tool - Reshape the wire format with Serde attributes without changing your idiomatic Rust field names
- Serialize the same type to TOML, YAML, MessagePack, bincode, and CSV by swapping the format crate
- Write
Serialize/Deserializeby hand when the derive is not enough, including for types you do not own - Profile and tune serialization with borrowing, zero-copy, streaming, and buffer reuse
Prerequisites
Section titled “Prerequisites”- Section 08: Error Handling — Serde’s
from_str/to_stringreturnResult, and deserialization errors are values you handle with?,match, or anyhow/thiserror. The error-handling vocabulary from Section 08 is assumed throughout. - Helpful but not required: Section 09: Generics & Traits —
Serialize/Deserializeare traits, and the derive vs. hand-written distinction lands better once trait basics and the orphan rule are familiar.
Estimated Time
Section titled “Estimated Time”- Reading: 4-5 hours
- Hands-on Practice: 3 hours
- Exercises: 2 hours
- Total: 8-10 hours
Tip: Read
serde-intro→serde-basics→derive-serialize→jsonas a single connected track — that covers the 90% case of typed JSON. Pull injson-manipulationwhen you need untyped JSON andattributeswhen the wire format and your Rust names diverge. Treatother-formats,custom-serialization, andperformanceas a toolbox to reach for when a specific need arises, rather than something to master up front.
Next: Section 16: Web APIs → — building HTTP clients and servers, where Serde does the request/response (de)serialization you just learned.