Skip to content

第 17 章 serde_json 源码:手写解析器如何接入 Data Model

17.1 从另一个角度看 Serde

前面 16 章都从 serde 本身的视角讲——Data Model、trait 设计、derive 宏如何生成代码。本章换个角度:一个具体格式(JSON)如何实现 Serializer 和 Deserializer

serde_json 是 Serde 生态最知名的格式 crate——几乎所有 Rust Web 服务都在用它。它的核心入口文件约 10,300 行 Rust(本书版本 1.0.149,wc -l src/*.rs src/io/*.rs 实测),完整 src/ 加上 lexical/ 等子模块见 §17.12.1 的尺寸表。本章不逐行讲这些源码,而是聚焦一个核心问题:

一个格式实现者如何把自己"接入" Serde?

回答这个问题需要看三部分源码:

  1. serde_json::Serializer(ser.rs,2285 行):如何实现 Serializer trait
  2. serde_json::Deserializer(de.rs,2704 行):如何实现 Deserializer trait
  3. serde_json::Value(value/ 目录):如何用 enum 表示任意 JSON

读完本章你会建立一个具体参照系——"当我要为我的格式写 Serde 支持时,代码应该长这样"。

本书基于 serde_json 1.0.149(commit dc8003a)。

17.2 源码结构

先看文件组织:

serde_json/src/
├── lib.rs          441 行  入口 + re-export
├── ser.rs         2285 行  Serializer + Formatter + to_string/to_writer 等
├── de.rs          2704 行  Deserializer + 所有 visit 路径
├── read.rs        1089 行  Read trait + SliceRead/StrRead/IoRead
├── value/         子目录   Value enum + 它的 Serialize/Deserialize
│   ├── mod.rs     Value 定义
│   ├── de.rs      Value 的 Deserialize 实现
│   ├── ser.rs     Value 的 Serialize 实现
│   └── ...
├── map.rs         1181 行  Map<String, Value>(保持插入顺序的 HashMap)
├── number.rs       814 行  Number 类型(i64/u64/f64 统一表示)
├── error.rs        541 行  Error 类型
├── raw.rs          784 行  RawValue(不解析的 JSON 片段)
├── macros.rs       303 行  json! 宏
├── iter.rs          70 行  Deserializer::into_iter
└── lexical/        子目录  浮点数格式化库

三大支柱

  • ser.rsde.rs对外接口——实现 Serde trait,让任何数据结构能 to/from JSON。
  • value/动态树结构——如果你不知道 JSON 长什么样,可以先解析成 Value,运行时探索。
  • read.rs输入抽象——无论从 &str、&[u8]、io::Read 都可以统一接口。

本章聚焦前两个read.rs 作为支持部分会略微提到。

17.3 入口函数:from_str 和 to_string

最常用的两个函数(serde_json/src/de.rs:2699ser.rs:2245):

rust
// 简化版
pub fn from_str<'a, T>(s: &'a str) -> Result<T>
where T: de::Deserialize<'a>,
{
    from_trait(read::StrRead::new(s))
}

pub fn to_string<T>(value: &T) -> Result<String>
where T: ?Sized + Serialize,
{
    let mut writer = Vec::with_capacity(128);
    to_writer(&mut writer, value)?;
    // SAFETY: writer 内容总是有效 UTF-8
    let string = unsafe { String::from_utf8_unchecked(writer) };
    Ok(string)
}

from_strStrRead 包装 &'a str'a 就是第 15 章的 'de),然后调用通用 from_trait 函数做实际反序列化。

to_string 先写入 Vec<u8>,然后 unsafe 转 String——因为 serde_json 保证输出总是 valid UTF-8(所有字符串字段都是 str,非字符串字段都是 ASCII)。这个 unsafe 优化省掉 UTF-8 验证(否则 from_utf8 要遍历整个字节数组)。

17.4 Serializer 实现:2285 行做什么

serde_json::Serializer 的 trait 实现是本章的核心之一。看它的结构(serde_json/src/ser.rs:17):

rust
pub struct Serializer<W, F = CompactFormatter> {
    writer: W,
    formatter: F,
}

impl<W: io::Write, F: Formatter> ser::Serializer for &'a mut Serializer<W, F> {
    type Ok = ();
    type Error = Error;

    type SerializeSeq = Compound<'a, W, F>;
    type SerializeTuple = Compound<'a, W, F>;
    type SerializeTupleStruct = Compound<'a, W, F>;
    type SerializeTupleVariant = Compound<'a, W, F>;
    type SerializeMap = Compound<'a, W, F>;
    type SerializeStruct = Compound<'a, W, F>;
    type SerializeStructVariant = Compound<'a, W, F>;
    // ↑ 所有 7 个关联类型都是 Compound——复用同一个状态机类型

    fn serialize_bool(self, value: bool) -> Result<()> {
        self.formatter.write_bool(&mut self.writer, value).map_err(Error::io)
    }

    fn serialize_i8(self, value: i8) -> Result<()> {
        self.formatter.write_i8(&mut self.writer, value).map_err(Error::io)
    }

    // ... 共 30 个方法,每个都是"调 formatter 的对应方法"
}

三个设计要点

1. 双层结构:Serializer + Formatter

Serializer 负责语义(把 Data Model 翻译成 JSON 结构),Formatter 负责格式(具体怎么写字节——紧凑 vs 缩进、转义策略)。

rust
pub trait Formatter {
    fn write_bool<W: io::Write>(&mut self, writer: &mut W, value: bool) -> io::Result<()>;
    fn write_i8<W: io::Write>(&mut self, writer: &mut W, value: i8) -> io::Result<()>;
    // ...
    fn begin_object<W: io::Write>(&mut self, writer: &mut W) -> io::Result<()>;  // 写 {
    fn end_object<W: io::Write>(&mut self, writer: &mut W) -> io::Result<()>;    // 写 }
    fn begin_object_key<W: io::Write>(&mut self, writer: &mut W, first: bool) -> io::Result<()>;
    fn end_object_key<W: io::Write>(&mut self, writer: &mut W) -> io::Result<()>;
    fn begin_object_value<W: io::Write>(&mut self, writer: &mut W) -> io::Result<()>;
    fn end_object_value<W: io::Write>(&mut self, writer: &mut W) -> io::Result<()>;
    // ... 其他 begin/end 方法
}

两个实现

  • CompactFormatter:紧凑输出 {"a":1,"b":2}
  • PrettyFormatter:缩进输出 {\n "a": 1,\n "b": 2\n}

用户选 to_string_pretty 还是 to_string 决定用哪个 formatter。

这种"语义/格式" 解耦让 serde_json 的同一套核心逻辑服务两种输出风格。也让用户可以自定义 Formatter(自己实现 Formatter trait,改变缩进、引号风格等)——一个扩展口。

2. 所有 7 个 SerializeXxx 都是 Compound

rust
pub enum Compound<'a, W: 'a, F: 'a> {
    Map { ser: &'a mut Serializer<W, F>, state: State },
    // 在某些 feature 下还有其他变体
}

enum State {
    Empty,
    First,
    Rest,
}

一个 Compound 同时服务 Seq、Tuple、Map、Struct 等——通过内部 State 标记进度。这让代码量显著减少(不需要为每种状态机写独立类型)。

看它同时实现 SerializeSeq 和 SerializeStruct:

rust
impl SerializeSeq for Compound<'_, W, F> {
    type Ok = ();
    type Error = Error;

    fn serialize_element<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<()> {
        match *self {
            Compound::Map { ref mut ser, ref mut state } => {
                ser.formatter
                    .begin_array_value(&mut ser.writer, *state == State::First)  // 写 , 或 nothing
                    .map_err(Error::io)?;
                *state = State::Rest;
                tri!(value.serialize(&mut **ser));  // 递归序列化元素
                ser.formatter
                    .end_array_value(&mut ser.writer)
                    .map_err(Error::io)?;
                Ok(())
            }
        }
    }

    fn end(self) -> Result<()> {
        match self {
            Compound::Map { ser, state } => {
                match state {
                    State::Empty => {}
                    _ => tri!(ser.formatter.end_array(&mut ser.writer).map_err(Error::io)),
                }
                Ok(())
            }
        }
    }
}

注意 end_array 依赖 state——如果一个元素都没序列化过(state == Empty),跳过 end_array;否则正常写 ]。这是处理"空数组"边缘情况的优雅方式。

3. 状态机通过 State enum 手动管理

rust
enum State {
    Empty,    // 还没有元素
    First,    // 第一个元素(不写前置逗号)
    Rest,     // 非第一个元素(写前置逗号)
}

这个 State 穿梭在 serialize_elementserialize_field 等方法之间。每次添加元素前检查 State,决定要不要先写分隔符。写完把 State 推进到 Rest。

手动状态管理是 JSON 序列化必然要做的事——因为 JSON 的 [1,2,3] 里元素之间有逗号,第一个没有、最后一个也没有(JSON 不接受尾随逗号)。State 机记住"我现在在哪",让每次调用决策简单。

17.5 真实序列化流程追踪

用户代码:

rust
let user = User { id: 1, name: "alice".into() };
let json = serde_json::to_string(&user)?;

调用栈(简化)

  1. to_string(&user) → 创建 Vec<u8> → 调 to_writer(&mut vec, &user)
  2. to_writer(w, value) → 创建 Serializer::new(w)value.serialize(&mut serializer)
  3. User::serialize(&self, &mut serializer) → 走 derive 生成的代码
  4. 生成代码调 serializer.serialize_struct("User", 2) → 返回 Compound::Map(state=Empty)
  5. 生成代码调 compound.serialize_field("id", &self.id)
    • formatter 写 { + "id" + :
    • 递归 1u64.serialize(&mut *serializer) → serialize_u64 → formatter.write_u64 写 1
    • state 推进到 First
  6. 生成代码调 compound.serialize_field("name", &self.name)
    • formatter 写 , + "name" + :
    • 递归 "alice".serialize(...) → serialize_str → formatter.write_string 写 "alice"
    • state 推进到 Rest
  7. 生成代码调 compound.end() → formatter 写 }
  8. 最终 writer 里是 {"id":1,"name":"alice"}
  9. to_string 把 writer unsafe 转 String 返回

每一步都是 Data Model 协议的调用 + Formatter 的字节写入。第 3 章的抽象在这里完全落地。

17.6 Deserializer 实现:2704 行做什么

serde_json::Deserializer 比 Serializer 复杂——反序列化要处理"解析 JSON token 流"。

rust
// serde_json/src/de.rs:31
pub struct Deserializer<R> {
    read: R,
    scratch: Vec<u8>,
    remaining_depth: u8,
    // ...
}

impl<'de, 'a, R: read::Read<'de>> de::Deserializer<'de> for &'a mut Deserializer<R> {
    type Error = Error;

    fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
        let peek = match tri!(self.parse_whitespace()) {
            Some(b) => b,
            None => return Err(self.peek_error(ErrorCode::EofWhileParsingValue)),
        };

        let value = match peek {
            b'n' => { /* null */ ... visitor.visit_unit() ... }
            b't' | b'f' => { /* true/false */ ... visitor.visit_bool(...) ... }
            b'"' => { /* string */ ... visitor.visit_borrowed_str / visit_string ... }
            b'[' => { /* array */ ... visitor.visit_seq(SeqAccess { ... }) ... }
            b'{' => { /* object */ ... visitor.visit_map(MapAccess { ... }) ... }
            b'0'..=b'9' | b'-' => { /* number */ ... visitor.visit_u64/i64/f64 ... }
            _ => return Err(self.peek_error(ErrorCode::ExpectedSomeValue)),
        };

        value
    }

    // deserialize_bool, deserialize_i32, ... 大多数直接转发到 deserialize_any
    // 或者做类型特定优化
}

关键机制

1. deserialize_any 是 JSON 反序列化的主引擎

因为 JSON 是自描述格式(见第 4 章)——看到 "...." 知道是字符串、[...] 知道是数组、{...} 知道是对象。deserialize_any 根据下一个字节判断类型,调用对应的 visit 方法。

2. 其他 deserialize_* 方法可能直接转发或做优化

rust
fn deserialize_bool<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
    let peek = match tri!(self.parse_whitespace()) {
        Some(b) => b,
        None => return Err(self.peek_error(ErrorCode::EofWhileParsingValue)),
    };

    let value = match peek {
        b't' => {
            self.eat_char();
            tri!(self.parse_ident(b"rue"));
            visitor.visit_bool(true)
        }
        b'f' => {
            self.eat_char();
            tri!(self.parse_ident(b"alse"));
            visitor.visit_bool(false)
        }
        _ => Err(self.peek_invalid_type(&visitor)),  // 不是 bool,错误指向期望类型
    };
    // ...
}

deserialize_bool 不走 any——它立刻验证下一个字节是 't' 或 'f',不是就报"invalid type"错。这种优化让"类型确定时"的反序列化快很多——不用建立 number/string 等一堆中间状态。

3. SeqAccess 和 MapAccess 的实现

看数组解析(简化):

rust
struct SeqAccess<'a, R: 'a> {
    de: &'a mut Deserializer<R>,
    first: bool,
}

impl<'de, 'a, R: Read<'de> + 'a> de::SeqAccess<'de> for SeqAccess<'a, R> {
    type Error = Error;

    fn next_element_seed<T: DeserializeSeed<'de>>(&mut self, seed: T) -> Result<Option<T::Value>> {
        let peek = match tri!(self.de.parse_whitespace()) {
            Some(b) => b,
            None => return Err(self.de.peek_error(ErrorCode::EofWhileParsingList)),
        };

        if peek == b']' {
            // 数组结束
            self.de.eat_char();
            return Ok(None);
        }

        if !self.first {
            // 不是第一个元素,必须有 ','
            if peek != b',' {
                return Err(self.de.peek_error(ErrorCode::ExpectedListCommaOrEnd));
            }
            self.de.eat_char();  // 消耗 ','
        }
        self.first = false;

        // 解析下一个元素
        Ok(Some(tri!(seed.deserialize(&mut *self.de))))
    }
}

逻辑就是"解析一个元素,返回 Some(element) 或 None(遇到 ])"。状态机的精度体现在每种出错情况都有独立错误码EofWhileParsingListExpectedListCommaOrEnd 等)——方便用户调试。

17.7 read::Read trait:输入抽象

JSON 可能从多种源来——&str&[u8]io::Read。serde_json 用 read::Read trait 统一处理:

rust
// serde_json/src/read.rs
pub trait Read<'de> {
    fn next(&mut self) -> Result<Option<u8>>;  // 读一个字节
    fn peek(&mut self) -> Result<Option<u8>>;  // 看下一个字节(不消耗)

    // 读字符串,可能借用
    fn parse_str<'s>(&'s mut self, scratch: &'s mut Vec<u8>) -> Result<Reference<'de, 's, str>>;

    // ...
}

pub enum Reference<'b, 'c, T: ?Sized + 'static> {
    Borrowed(&'b T),   // 借用原输入
    Copied(&'c T),     // 从 scratch 借用
}

三种实现

  • StrRead<'a>:从 &'a str 读。可以 borrow 原字节(Reference::Borrowed)。
  • SliceRead<'a>:从 &'a [u8] 读。类似 StrRead。
  • IoRead<R>:从 io::Read 读。必须复制(读过就走了),只能 Reference::Copied

第 15 章讲的借用反序列化在这里有具体支撑。StrRead 的 parse_str 返回 Reference::Borrowed(&'de str) → visitor 的 visit_borrowed_str 被调用 → &'de str 字段可以零拷贝。IoRead 的 parse_str 必须把字节复制到 scratch,返回 Reference::Copied(&'c str) → visitor 的 visit_str(非 borrowed)→ &'de str 字段的 visitor 报 invalid_type 错。

这个设计把"能否借用"的选择权放到了Read 实现层——上层的 Visitor 被动接受。零拷贝能力从格式、输入源、数据类型三方协商——第 15 章讲的"三方博弈"在这里有源码层的支撑。

17.8 字符串解析的精细:转义处理

JSON 字符串可能有转义序列(\n\t\"\uFFFF)。解析时:

无转义字符串"alice"——直接借用原字节。

有转义字符串"hello\nworld"——必须解码,写到 scratch 里:

rust
// serde_json/src/read.rs (精简)
fn parse_str<'s>(&'s mut self, scratch: &'s mut Vec<u8>) -> Result<Reference<'de, 's, str>> {
    let start = self.index;
    loop {
        match self.peek()? {
            Some(b'"') => {
                // 如果没遇到过转义,直接借用
                if scratch.is_empty() {
                    let s = &self.slice[start..self.index];
                    self.index += 1;  // 跳过 "
                    return Ok(Reference::Borrowed(unsafe { str::from_utf8_unchecked(s) }));
                } else {
                    // 之前遇到过转义,已经复制到 scratch
                    scratch.extend_from_slice(&self.slice[start..self.index]);
                    self.index += 1;
                    return Ok(Reference::Copied(unsafe { str::from_utf8_unchecked(scratch) }));
                }
            }
            Some(b'\\') => {
                // 遇到转义 — 首次遇到时复制前置部分到 scratch
                scratch.extend_from_slice(&self.slice[start..self.index]);
                self.parse_escape(scratch)?;  // 解码转义写进 scratch
                start = self.index;  // 更新起点
            }
            _ => self.index += 1,
        }
    }
}

逻辑:一路扫描,遇到 " 结束、遇到 \ 时把已扫描部分复制到 scratch、解码转义后继续。无转义的字符串完全零拷贝;有转义的最少复制(只复制到 scratch 里一次)

**这种"optimistic borrowing"**是 serde_json 性能的重要来源:常见 JSON 字符串没有反斜杠转义时,可以直接借用输入切片;一旦遇到转义,再退回 scratch 缓冲。具体命中率取决于业务数据,日志、自然语言、URL、Unicode 转义密集的输入会有完全不同的表现。

17.9 Value:动态 JSON 表示

serde_json::Valuevalue/mod.rs)是"通用 JSON 对象":

rust
pub enum Value {
    Null,
    Bool(bool),
    Number(Number),
    String(String),
    Array(Vec<Value>),
    Object(Map<String, Value>),
}

用途

  • 当你不知道 JSON 形状:解析成 Value,运行时探索。
  • 当你要动态构造 JSON:用 json! 宏或手动构建 Value 树。
  • 当你要转换:从一个 struct 转 JSON 再转另一个 struct(serde_json::from_value(to_value(x)?))。

Value 的 Serialize 实现value/ser.rs,简化):

rust
impl Serialize for Value {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        match *self {
            Value::Null => serializer.serialize_unit(),
            Value::Bool(b) => serializer.serialize_bool(b),
            Value::Number(ref n) => n.serialize(serializer),
            Value::String(ref s) => serializer.serialize_str(s),
            Value::Array(ref v) => v.serialize(serializer),
            Value::Object(ref m) => {
                use serde::ser::SerializeMap;
                let mut map = serializer.serialize_map(Some(m.len()))?;
                for (k, v) in m {
                    map.serialize_entry(k, v)?;
                }
                map.end()
            }
        }
    }
}

match 分派到对应 Serializer 方法——每种 Value 变体映射到 Data Model 的某个原语。

Value 的 Deserialize 实现value/de.rs,简化):

rust
impl<'de> Deserialize<'de> for Value {
    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Value, D::Error> {
        struct ValueVisitor;
        impl<'de> Visitor<'de> for ValueVisitor {
            type Value = Value;

            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
                f.write_str("any valid JSON value")
            }

            fn visit_bool<E: Error>(self, v: bool) -> Result<Value, E> { Ok(Value::Bool(v)) }
            fn visit_i64<E: Error>(self, v: i64) -> Result<Value, E> { Ok(Value::Number(v.into())) }
            fn visit_u64<E: Error>(self, v: u64) -> Result<Value, E> { Ok(Value::Number(v.into())) }
            fn visit_f64<E: Error>(self, v: f64) -> Result<Value, E> { Ok(Value::Number(Number::from_f64(v).unwrap())) }
            fn visit_str<E: Error>(self, v: &str) -> Result<Value, E> { Ok(Value::String(v.to_owned())) }
            fn visit_string<E: Error>(self, v: String) -> Result<Value, E> { Ok(Value::String(v)) }
            fn visit_none<E: Error>(self) -> Result<Value, E> { Ok(Value::Null) }
            fn visit_some<D: Deserializer<'de>>(self, d: D) -> Result<Value, D::Error> { Deserialize::deserialize(d) }
            fn visit_unit<E: Error>(self) -> Result<Value, E> { Ok(Value::Null) }

            fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Value, A::Error> {
                let mut vec = Vec::new();
                while let Some(elem) = seq.next_element()? { vec.push(elem); }
                Ok(Value::Array(vec))
            }

            fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Value, A::Error> {
                let mut values = Map::new();
                while let Some((k, v)) = map.next_entry()? { values.insert(k, v); }
                Ok(Value::Object(values))
            }
        }

        d.deserialize_any(ValueVisitor)
    }
}

关键d.deserialize_any(ValueVisitor) —— 调用 deserialize_any,让 Deserializer 根据输入形态分派到 visitor 的对应方法。这也是 Value 只支持自描述格式(JSON、YAML、MessagePack)而不能从 Bincode 反序列化的原因——Bincode 没有 deserialize_any(第 4 章讲过)。

17.9.1 Number 的内部三态:N::PosInt / NegInt / Float

上面 Value::Number(Number)Number 看着像一个简单数字容器——实际打开 serde-json/src/number.rs:22 会看到它是一个刻意设计的三态代数类型

rust
pub struct Number {
    n: N,
}

#[cfg(not(feature = "arbitrary_precision"))]
enum N {
    PosInt(u64),
    /// Always less than zero.
    NegInt(i64),
    /// Always finite.
    Float(f64),
}

#[cfg(feature = "arbitrary_precision")]
type N = String;

三态的存储选择不是随手定的:

  • PosInt(u64)——正整数用 u64,可表示 0 到 2^64 - 1。如果用 i64 只能到 2^63 - 1、一半的正数范围浪费掉。
  • NegInt(i64)——严格负整数(源码注释 "Always less than zero")。用 i64 能表示 -2^63-1
  • Float(f64)——源码注释 "Always finite"——NaN+inf-inf 永远不会存进这个变体。构造函数(如 from_f64)会显式拒绝非有限值。

两个"总是"约束不是描述性的 docstring、是代码层面的不变式——正因为有这两个约束,N 才能实现 Eq(line 50):

rust
// Implementing Eq is fine since any float values are always finite.
#[cfg(not(feature = "arbitrary_precision"))]
impl Eq for N {}

NaN 有 "NaN != NaN" 的反射性破坏——如果 Float 可能是 NaN、Eq(要求反射性)就不能实现。Serde_json 通过在构造时拒绝 NaN换来"Number 可以用作 HashMap 的 key"——用户失去"在 JSON 里表示 NaN"的能力(JSON 规范也不支持)、换来 Number 的 Hash + Eq 语义完整性。

17.9.2 +0.0 / -0.0 的 Hash 归一化

NHash 实现(line 53-69)藏着 IEEE 754 浮点数规范的一个经典坑:

rust
impl Hash for N {
    fn hash<H: Hasher>(&self, h: &mut H) {
        match *self {
            N::PosInt(i) => i.hash(h),
            N::NegInt(i) => i.hash(h),
            N::Float(f) => {
                if f == 0.0f64 {
                    // There are 2 zero representations, +0 and -0, which
                    // compare equal but have different bits. We use the +0 hash
                    // for both so that hash(+0) == hash(-0).
                    0.0f64.to_bits().hash(h);
                } else {
                    f.to_bits().hash(h);
                }
            }
        }
    }
}

IEEE 754 里 +0.0-0.0 是不同位模式但 == 相等。按 Rust 的 Hash-Eq 契约——a == b 必须蕴含 hash(a) == hash(b)——直接用 to_bits() 会违反契约(两个位模式不同的 f64 得到不同 hash)。源码的解决是检测 f == 0.0 就统一用 +0.0 的 bits 做 hash——让两个零的 hash 相同。

这 10 行代码是 Rust 程序员"读标准 + 读 f64 语义"修炼的试金石:

  • 不读 IEEE 754:看不出为什么要这个 if
  • 读了 IEEE 754 但没读 Rust Hash-Eq 契约:看不出不做归一的后果
  • 两个都读了:才明白这是不可省略的正确性修补

17.9.3 arbitrary_precision feature:String 作存储的另一面

最后一个 cfg 分支(line 72-73)值得一看:

rust
#[cfg(feature = "arbitrary_precision")]
type N = String;

用户 features = ["arbitrary_precision"] 开启后、Number 不再按类型分三态、而是直接存原始 JSON 文本。目的:保留任意精度——比如 JSON 里出现 123456789012345678901234567890(超出 u64)或 0.1 + 0.2 这种浮点精度问题,标准三态表示必然丢精度;存成 String 可以保留原样、延迟到用户显式转类型时再 round-trip。

代价也很真实——+/-/* 等数值操作全部不可用(String 不支持),所有 as_i64() 类访问都要走字符串解析。这个 feature 在高精度金融 / 科学计算场景必选、在普通 Web API场景就是多余开销。一个 type alias 分支把两种完全不同的存储策略压进同一个 API——用户代码不变、只改 Cargo.toml

这三个细节(三态代数存储 / Hash 归一 / arbitrary_precision feature 切换)合在一起是 "JSON Number 在 Rust 里怎么做才正确"的完整答案——每一条都对应一个容易忽略但影响正确性或表达力的具体问题。

17.10 性能工程:lexical 浮点数库

serde_json 源码里有一个 lexical/ 目录(实测 3708 行、9 个 .rs 文件)。它是什么?

IEEE 754 浮点数的字符串往返格式化——非常难。直接用 Rust 标准库的 {} 格式化 f64 可能不是最优(字符串更长、精度可能不完全)。serde_json 引入了一个独立的 lexical 实现,在浮点数→字符串的往返中保证:

  1. 最短表示0.1f64 格式化为 "0.1" 而不是 "0.1000000000000000055511151231257827021181583404541015625"
  2. 往返精确f64 → str → f64 得回完全一致的值(比特一致)。
  3. 比标准库快:对浮点数 Display 格式化有显著提升(社区测评通常报告 2-5 倍量级,具体依赖浮点值形态;参考 Ryū 论文的基准测试)。

这 3708 行代码是 "performance-critical" 工程的典型。对于浮点字段密集的 JSON,数值格式化会成为关键路径;对于字符串或对象结构占主导的 JSON,瓶颈可能转向转义、map 遍历或写入器。优化它的价值来自"浮点格式化很难且经常出现在热路径",不来自某个脱离场景的固定比例。

启示:一个好的格式实现需要的不只是"实现 Serde trait"——还要工业级的底层工程。数值格式化、字符串转义、错误定位(行列号)、内存分配策略——每一处都是性能/质量的关键。

17.11 to_value / from_value:中间类型的双向转换

serde_json 提供一个特别有用的函数对:

rust
pub fn to_value<T: Serialize>(value: T) -> Result<Value>;
pub fn from_value<T: DeserializeOwned>(value: Value) -> Result<T>;

to_value:把任何 Serialize 类型转成 Value 树。 from_value:把 Value 树转成任何 DeserializeOwned 类型。

实现原理:它们内部创建一个"Value 作为 Serializer 或 Deserializer"的 adapter:

rust
// to_value 伪代码
pub fn to_value<T: Serialize>(value: T) -> Result<Value> {
    value.serialize(Serializer)  // Serializer 是一个构造 Value 的 Serializer
}

// Serializer 定义
struct Serializer;
impl ser::Serializer for Serializer {
    type Ok = Value;
    type Error = Error;
    fn serialize_bool(self, v: bool) -> Result<Value> { Ok(Value::Bool(v)) }
    fn serialize_str(self, v: &str) -> Result<Value> { Ok(Value::String(v.to_owned())) }
    fn serialize_seq(self, _: Option<usize>) -> Result<SerializeVec> { Ok(SerializeVec::new()) }
    // ...
}

注意 type Ok = Value——Serializer 的 Ok 是返回值!不是 ()(写入副作用)。这是第 3 章提到过的"构建式 Serializer"——用 type Ok 传递返回值。

用途:任何 Serialize 类型都可以转成 Value 做运行时处理:

rust
let user = User { id: 1, name: "alice".into() };
let mut value = serde_json::to_value(&user)?;
value["extra"] = json!("metadata");  // 运行时修改
let modified_user: UserExtended = serde_json::from_value(value)?;

性能代价:这一对函数内部构建完整 Value 树——对大对象有显著内存开销。但功能价值远超性能代价——很多应用 API 需要"拿到数据、做点运行时判断、再重新发给别的 API",用 Value 是最自然的表达。

17.12 json! 宏

serde_json 还提供一个声明宏方便构造 Value:

rust
use serde_json::json;

let v = json!({
    "status": "ok",
    "code": 200,
    "data": {
        "id": 1,
        "tags": ["a", "b", "c"]
    }
});
// v: Value

实现在 macros.rs(303 行声明宏)——第 5 章讲过这是声明宏而不是过程宏。它递归匹配 JSON-like 语法,展开成 Value::Object / Value::Array 等构造调用。

声明宏的典型应用——不需要 AST 操作,只是"模板化构造"。对这种场景,声明宏足够用,且编译快得多。

17.12.1 serde_json 整 crate 18276 行的真实尺寸表

把整个 serde-rs/json 仓库 src/ 按文件大小排序——

文件角色
de.rs2704Deserializer + 字符串转义 + 数字解析 + 错误恢复(§17.6 主角)
ser.rs2285Serializer + 9 种 SerializeXxx Compound + Formatter trait(§17.4 主角)
value/de.rs1507Value → T 反序列化(DeserializeAny 的 in-memory 路径)
map.rs1181serde_json::Map——BTreeMap / IndexMap 的 wrapper(feature 切换)
read.rs1089StrRead / SliceRead / IoRead 三套 Read trait(§17.7)
value/ser.rs1063T → Value 序列化
value/mod.rs1042Value enum 定义 + 各种 as_x() 访问器
lexical/math.rs884大整数运算(浮点格式化的内部表示)
number.rs814Number 类型(§17.9.1)
raw.rs784RawValue——延迟解析的 newtype(用于子 JSON 透传)
lexical/large_powers64.rs625预计算的 10 的幂表(64-bit)
error.rs541Error 类型 + 行列号定位
lib.rs441crate 入口 + from_str / to_string / to_value 等顶层 API
lexical/num.rs421数字 trait 抽象
macros.rs303json! 宏的 token tree 递归实现
value/from.rs284From<i32> for Value 等 70+ impl
value/index.rs258Index trait 让 value["a"]["b"] 可写
其余 lexical 7 文件132~231rounding / cached_float80 / bhcomp / algorithm / float / large_powers32 / errors
io/core.rs + iter.rs 等小文件≤ 100no_std 兼容 + 工具
总计18276

四条值得记住的物理事实——

  1. de.rs > ser.rs(2704 vs 2285)——反序列化比序列化大 19%——根因是反序列化要做两件序列化没有的事:(a) 字节流的字符级状态机(去 BOM、跳空白、识别字面量);(b) 错误恢复(位置追踪、unexpected_token 报告)——error.rs 541 行有近一半在算 line/column
  2. value/de.rs(1507)比 value/ser.rs(1063)大 41%——因为 Value → T 的反序列化要为Value 的每种 variant 写一遍 visit_xxx 方法(数字 → i64/u64/f64 / bool / 字符串 / null / map / seq);T → Value 反过来只需要"接收 visit_xxx 调用、组装成 Value enum"——visitor 模式的不对称代价
  3. lexical 子目录 3708 行 = 整 crate 20%——纯粹用于浮点格式化——其中 large_powers64.rs 625 + large_powers32.rs 183 = 808 行只是预计算 10 的幂查表——为了避免运行时算 pow(10, n) 损失精度
  4. raw.rs 单独 784 行——RawValue 是个看似简单的 newtype({"raw": "<bytes>"})、但要正确实现 Serialize/Deserialize 需要和 serde 内部的 __private 字段名约定握手——所以 serde 主仓库里有 Content / ContentDeserializer 专门为 RawValue 服务(§14 讲过)

17.12.2 三个容易忽视的格式边界:Map、RawValue、StreamDeserializer

理解 serde_json 不能只看 from_str / to_string。真实项目里经常踩坑的,是三个边界类型:对象 map、原始 JSON 片段、连续 JSON 流。

第一,Map<String, Value> 默认不是插入有序。 serde_json/src/map.rs:1-4 的模块注释写明:默认 backing 是 BTreeMap,开启 preserve_order feature 才改用 IndexMap。类型别名在 serde_json/src/map.rs:33-36 落地:未开启 feature 时 MapImpl<K, V> = BTreeMap<K, V>,开启时是 IndexMap<K, V>

这影响所有依赖 Value::Object 的代码。你如果把 JSON 当"有序配置文件"处理,默认 serde_json::Value 会按 key 排序,而不是保留输入顺序。想保留顺序,要在依赖里显式开启 feature。这个行为不是文档角落里的小字,而是 map 类型的源码结构决定的。

serde_json/src/map.rs:491-503Map<String, Value> 实现 Serialize,内部调用 serializer.serialize_map(Some(self.len())) 并逐项 serialize_entryserde_json/src/map.rs:506-536 给它实现 Deserialize,用 Visitor 的 visit_map 循环填充新 map。serde_json/src/map.rs:601-615 还为 owned 和 borrowed map 实现 IntoDeserializer,这让内存里的 Value::Object 可以再次作为反序列化输入,支撑 from_value 这类 API。

第二,RawValue 是"延迟解析"边界,不是普通字符串。 serde_json/src/raw.rs:18-27 说明它用于推迟解析,或者完全不解析、只把一段 payload 原样转发;序列化时保留原始格式,不会被 minify 或 pretty-print。serde_json/src/raw.rs:81-100 给出两个形态:从 from_str / from_slice 进入时可以借用 &RawValue,从 from_reader 进入时必须用 boxed 形态,因为 I/O 流需要把原始片段缓冲到内存。

serde_json/src/raw.rs:116-132RawValue 定义成 repr(transparent)str newtype,并用受控的 transmute&str / Box<str>RawValue 之间转换。这段实现说明 RawValue 的语义是"这段字符串已经被验证为 JSON 值",不是"任意字符串以后再说"。它适合网关、日志管道、签名透传这类场景:外层结构由 Serde 检查,内层大 JSON 片段保持原貌。

第三,StreamDeserializer 是连续值协议的边界。 serde_json/src/de.rs:2330-2356 定义 StreamDeserializer,文档示例展示同一输入里连续放对象、数字、字符串、对象和数组;serde_json/src/de.rs:2434-2450Iterator::next 每次跳过空白后反序列化一个 T。这不是 JSON array,而是"多个自分界 JSON 值连续排列"。

这三个边界对应三类工程决策:

决策默认行为什么时候要显式调整
对象 key 顺序BTreeMap 排序需要保留输入顺序时启用 preserve_order
子 JSON 是否解析普通字段会完整解析只透传或延迟处理时用 RawValue
输入是否只有一个值from_str 期待一个完整值日志流、拼接响应、增量协议用 StreamDeserializer

把这些边界纳入设计,比在业务层补丁式处理更稳。比如 LLM 工具调用返回一串 JSON 片段时,不应先手写 split;应该用 StreamDeserializer 判断每个片段是否是完整 JSON 值。API 网关需要转发未知字段时,不应把大对象解析成 Value 再写回;如果只需要原样透传,可以考虑 RawValue。配置文件需要保留用户书写顺序时,不应假设 Value::Object 默认有序,而要明确 feature 选择。

还有一个与第 3 章呼应的边界:JSON map key。serde_json/src/ser.rs:2240-2244to_string 文档写明,序列化可能因为 map 含非字符串 key 而失败。这个错误不是 Value 层才会出现;任何 HashMap<K, V> 只要 K 的序列化结果不是 JSON 字符串,都可能在格式层被拒绝。Serde Data Model 允许 map key 是任意可序列化值,但 JSON 语法只允许字符串 key。格式 crate 的职责,就是在 Data Model 的宽表达力和具体格式的窄语法之间做裁决。

所以 serde_json 不是"Serde trait 的一个简单实现"。它同时承担四个约束转换:Rust 类型到 JSON 语法、Serde map 到字符串 key 对象、输入切片到可借用生命周期、连续字节流到一个个自分界 JSON 值。只看 Value enum 会低估它的复杂度;真正的格式实现难点,恰好藏在这些边界里。

这也解释了为什么 serde_json 适合作为学习其他格式 crate 的参照物。你先在 JSON 里看清这些边界,再去读 bincode、postcard、rmp-serde,就会知道哪些复杂度来自 Serde,哪些复杂度来自格式本身。

读格式 crate 时可以沿用同一张清单:值如何进入,复合结构如何保持状态,错误如何定位,借用是否可能,动态树和流式解析是否存在。serde_json 把这些问题都摆在明处。

掌握这张清单后,再选择 JSON、MessagePack、Bincode 或 Postcard,就不只是比较体积和速度,而是在比较各自能表达什么、不能表达什么、错误会在哪里出现。

格式选择最终是语义选择,也是故障边界选择。

17.13 和其他 Serde 格式 crate 的对比

知道 serde_json 的实现后,看其他格式 crate 会更快:

  • bincode:二进制紧凑格式。它的 Serializer 没有 Formatter 层——直接按二进制布局写。不支持 deserialize_any(非自描述)。代码量约 5000 行(serde_json 的一半)。
  • rmp-serde(MessagePack):二进制自描述格式。实现和 serde_json 类似,有 Formatter 对应 MessagePack 的不同变体(有无字段名等)。支持 deserialize_any
  • serde_yaml:基于 yaml-rust,JSON 风格但语法更复杂。代码最大(因为 YAML 语法复杂度远超 JSON)。
  • toml:TOML 格式。实现不同——TOML 是"无上下文的 key-value"格式,反序列化时常要预扫描。
  • postcard:极简二进制格式,针对嵌入式。高度优化,代码量约 3000 行。

所有 serde 格式 crate 共享同一套 Serde trait——用户只需学一次 API 就能切换任何格式。这是第 1 章讲的 M+N 架构的实际威力。

17.14 和丛书其他书的关联

serde_json 和丛书里多个书有深度关联:

  • 丛书《MCP 协议设计与实现》第 3 章"JSON-RPC 与消息格式":所有 MCP 消息通过 serde_json 序列化/反序列化。读过那本书再回来看本章,你能理解 MCP runtime 的每一次 JSON 往返用的就是本章讲的 Serializer/Deserializer。
  • 丛书《LangChain 设计与实现》:LangChain 的所有 Tool 调用、LLM response 都用 serde_json 解析。Agent 的"lose ends" 问题(处理 LLM 偶尔返回格式错误 JSON)很多时候就是本章讨论的错误恢复逻辑在用户层的表现。
  • 丛书《Tokio 源码深度解析》第 17 章 "可观测性":tracing 输出 JSON 日志时调用 serde_json——高频调用场景下 serde_json 的性能设计(Formatter 分离、lexical 浮点格式化)就是让 tracing 不成为性能瓶颈的直接原因。
  • 丛书卷一《Rust 编译器》第 7 章"trait 分发":serde_json Serializer 的每个方法都是泛型/单态化——编译器为每种"数据类型 × Serializer" 组合生成单独代码。这就是卷一单态化那一章的实际应用。

17.15 本章小结

serde_json 是 Serde 生态"格式实现"的参考标杆。本章讲了它的核心设计:

  1. 分层架构:Serializer(语义层)+ Formatter(格式层)——解耦"Data Model 转换"和"字节输出风格"。
  2. Compound 状态机:7 个 SerializeXxx 关联类型全部是 Compound——通过 State enum 管理进度,极大减少代码量。
  3. 手写 JSON 解析器:de.rs 2700+ 行手写字符、状态机、错误定位。deserialize_any 是主引擎,其他 deserialize 方法做类型特定优化。
  4. Read trait 的输入抽象:StrRead / SliceRead / IoRead 三种实现。StrRead 和 SliceRead 支持 Reference::Borrowed 零拷贝,IoRead 必须复制——这是第 15 章借用反序列化的底层支撑。
  5. Value 动态树:用 enum + DeserializeAny 支持运行时 JSON 操作。
  6. 性能工程:lexical 浮点库 3708 行 9 文件专门优化浮点格式化——说明"格式实现"不只是 trait 实现,还有工业级底层工程。

serde_json 整 crate 18276 行——de.rs (2704) + ser.rs (2285) + lexical/ (3708) 三块占 48%——是理解整个 JSON 实现的最小必读子集。

动手实验

  1. 测试 pretty 和 compact 的区别:用 serde_json::to_string_prettyto_string 序列化同一个对象,观察输出差异。然后读 ser.rs 的 PrettyFormatter 实现,看它如何添加换行和缩进。
  2. 手写一个 Formatter:实现一个 "YAML-like Formatter",让 serde_json 输出 YAML 风格的 JSON。不需要完整,实现 begin_object/end_object/begin_object_key 等几个方法就好。
  3. 测试 StrRead 和 IoRead 的性能差异:用同一份 JSON,一次 from_str、一次 from_reader(用 Cursor 包装同样的字节)。用 criterion 对比时间。注意不同的 struct(有 &str 字段 vs 全 String 字段)。
  4. 读 lexical/:打开浮点格式化库,理解"最短表示"算法(Grisu3 或 Ryū)。这是一个独立的小引擎,读懂它你能理解"为什么浮点格式化是个非平凡问题"。

延伸阅读

  • serde_json 仓库:完整源码。2700 行的 de.rs 是整个仓库最有"工程感"的一份代码。
  • JSON RFC 8259:JSON 格式规范,读懂 serde_json 对各种边缘情况(空对象、嵌套深度限制、Unicode 处理)的处理选择。
  • Ryū 浮点格式化论文:lexical 使用的浮点格式化算法。深度内容,但对性能工程师必读。
  • Other Serde formats:bincode、postcard、rmp-serde、serde_yaml 等格式 crate。
  • 丛书《MCP 协议设计与实现》第 3 章:看 serde_json 在真实 JSON-RPC 协议里的应用。
  • 丛书卷一《Rust 编译器》第 7 章:"trait 分发" 解释为什么 serde_json 的泛型方法能零成本。

基于 VitePress 构建