Skip to content

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.


  • How JSON.parse/JSON.stringify map onto Serde’s Serialize/Deserialize traits, and the data-model architecture that lets one type target every format
  • How to set up serde (with features = ["derive"]) and serde_json, and round-trip values with to_string/from_str
  • What #[derive(Serialize, Deserialize)] actually generates for structs and enums
  • How structs map to JSON, including nested types, Vec/HashMap, Option fields, and the four enum representations (externally/internally/adjacently tagged and untagged)
  • How to work with dynamic, schema-less JSON via serde_json::Value and the json! 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, use serialize_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, avoiding Value, and reusing buffers

TopicDescription
Serde IntroJSON.parse/JSON.stringify → Serde; the Serialize/Deserialize traits and the data-model architecture (data types ↔ formats).
Serde BasicsSetting 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 JSONStructs ↔ JSON: nested types, Vec/HashMap, Option fields, and the four enum representations.
Dynamic JSONSchema-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 FormatsThe same data in other formats: TOML, YAML (serde_norway), MessagePack (rmp-serde), bincode, and CSV.
Custom SerializationHand-written Serialize/Deserialize, serialize_with/deserialize_with, and remote derive.
PerformanceBorrowing (&str, #[serde(borrow)]), zero-copy, streaming, avoiding Value, and buffer reuse.

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 JSON object, and why it makes types format-agnostic
  • Add and configure serde and serde_json, and round-trip your own types with to_string, to_string_pretty, and from_str
  • Derive Serialize/Deserialize for 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 Value and the json! 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/Deserialize by 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

  • Section 08: Error Handling — Serde’s from_str/to_string return Result, 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 & TraitsSerialize/Deserialize are traits, and the derive vs. hand-written distinction lands better once trait basics and the orphan rule are familiar.

  • Reading: 4-5 hours
  • Hands-on Practice: 3 hours
  • Exercises: 2 hours
  • Total: 8-10 hours

Tip: Read serde-introserde-basicsderive-serializejson as a single connected track — that covers the 90% case of typed JSON. Pull in json-manipulation when you need untyped JSON and attributes when the wire format and your Rust names diverge. Treat other-formats, custom-serialization, and performance as 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.