写一个迷你 serde
主线位置:把序列化主线和反序列化主线各从零实现一遍。序列化一侧从
#[derive(Serialize)]生成的serialize_struct/serialize_field/end到写出 JSON 字节;反序列化一侧从from_str按首字节调visit_*到MapAccess逐键取值。缓冲主线(Content)不实现。
flowchart TB post[Post] -->|"derive 生成"| ser["serialize_struct 等"] ser --> writer["mini_json 写出器"] writer --> text["JSON 文本"] text --> parser["mini_json 解析器"] parser -->|"visit_map"| vis["Post 的 Visitor"] vis --> post2["Post"] text -.->|"逐字节比较"| sj["serde_json 输出"]
范围
数据模型只留五种类型,外加 struct(格式可以把它写成 map)。每一种对应 serde 里的一组方法:
| 迷你版 | Serializer | Visitor | serde 里同一位置还有 |
|---|---|---|---|
| bool | serialize_bool | visit_bool | |
| i64 | serialize_i64 | visit_i64 | i8–i128、u8–u128、f32、f64、char |
| str | serialize_str | visit_str、visit_borrowed_str | visit_string、bytes 三件套 |
| seq | serialize_seq + SerializeSeq | visit_seq + SeqAccess | tuple、tuple_struct |
| map | serialize_map + SerializeMap | visit_map + MapAccess | |
| struct | serialize_struct + SerializeStruct | 走 visit_map | unit、newtype、四种 enum 变体、option |
serde 的完整数据模型是 29 种类型,见 serde 解决什么问题。去掉的除了这些类型,还有 DeserializeSeed、EnumAccess、deserialize_in_place、is_human_readable 和整条缓冲主线,页尾有一张表。
代码分四个 crate,结构照 serde 和 serde_json 的分工:
| crate | 行数 | 内容 | 对应 |
|---|---|---|---|
mini_serde | 428 | 两组 trait 和标准库类型的 impl | serde_core |
mini_serde_derive | 64 | #[derive(Serialize)] | serde_derive |
mini_json | 536 | 写出器、解析器、错误类型 | serde_json 的 ser.rs、de.rs、read.rs |
compare | 78 | Post,同时 derive 两边的 Serialize |
mini_json 和 mini_serde 不依赖 serde(cargo tree -p mini_json 里只有 mini_serde、mini_serde_derive 和后者用的 proc-macro2 1.0.107、quote 1.0.47、syn 3.0.9,和 serde_derive 1.0.229 用的是同一代 syn)。只有 compare 同时依赖 serde =1.0.229 和 serde_json =1.0.154,用来对照。下文的代码都是 2 空格缩进的自写代码,引用 serde 源码的地方保留原样的 4 空格。
Serialize 与 Serializer
trait 的形状和 Serialize 与 Serializer 讲的一样,只是方法少:
pub trait Serialize {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>;
}
pub trait Serializer: Sized {
type Ok;
type Error: Error;
type SerializeSeq: SerializeSeq<Ok = Self::Ok, Error = Self::Error>;
type SerializeMap: SerializeMap<Ok = Self::Ok, Error = Self::Error>;
type SerializeStruct: SerializeStruct<Ok = Self::Ok, Error = Self::Error>;
fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error>;
fn serialize_i64(self, v: i64) -> Result<Self::Ok, Self::Error>;
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error>;
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error>;
fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error>;
fn serialize_struct(
self,
name: &'static str,
len: usize,
) -> Result<Self::SerializeStruct, Self::Error>;
}
pub trait SerializeStruct {
type Ok;
type Error: Error;
fn serialize_field<T: ?Sized + Serialize>(
&mut self,
key: &'static str,
value: &T,
) -> Result<(), Self::Error>;
fn end(self) -> Result<Self::Ok, Self::Error>;
}
写的时候才会碰到的几处:
- 每个方法按值拿
self,所以格式把Serializer实现在&mut mini_json::Serializer上,状态对象里再借一次。serde_json 也是这样(impl ser::Serializer for &'a mut Serializer<W, F>)。 - 三个状态对象的关联类型都要写
Ok = Self::Ok, Error = Self::Error。把SerializeSeq的这两个约束删掉试过:Serialize for [T]里serialize_element(element)?报`?` couldn't convert the error to `<S as Serializer>::Error`,seq.end()报expected `Serializer::Ok`, found `SerializeSeq::Ok`。 - 数据模型只有 i64,
u32之类在Serialize里先转成 i64 再调serialize_i64。serde 正好相反:每种整数一个方法,由格式决定怎么写。这里的代价是u64写不进来(i64::from对u64没有实现,宏里干脆没列它)。
macro_rules! int_impl {
($($ty:ty)*) => {$(
impl Serialize for $ty {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_i64(i64::from(*self))
}
}
)*};
}
int_impl!(i8 i16 i32 i64 u8 u16 u32);
Vec<T>、[T]、BTreeMap<K, V> 的 impl 就是 serialize_seq(Some(len))、逐个 serialize_element、end,和 serde 的 标准库类型的实现 同一个形状。
Impossible:不存在的状态对象
格式不支持某种复合类型时,关联类型也得填一个类型。serde 给的是 Impossible,里面放一个空 enum Void。迷你版用 std::convert::Infallible,效果一样:这个类型没有值,方法体写 match self.void {} 就能通过类型检查,永远不会被调用。
pub struct Impossible<Ok, E> {
void: std::convert::Infallible,
marker: std::marker::PhantomData<(Ok, E)>,
}
impl<Ok, E: Error> SerializeSeq for Impossible<Ok, E> {
type Ok = Ok;
type Error = E;
fn serialize_element<T: ?Sized + Serialize>(&mut self, _value: &T) -> Result<(), E> {
match self.void {}
}
fn end(self) -> Result<Ok, E> {
match self.void {}
}
}
下面的 map 键写出器用到它。
JSON 写出器
mini_json::Serializer 只有一个 String。serde_json 是一个 io::Write 加一个 Formatter(序列化一侧),迷你版不支持缩进,也就不需要 Formatter。
三种复合类型共用一个状态对象,它只需要记住一件事:下一个元素前面写不写逗号。
pub struct Compound<'a> {
ser: &'a mut Serializer,
first: bool,
}
impl Compound<'_> {
fn comma(&mut self) {
if !self.first {
self.ser.out.push(',');
}
self.first = false;
}
}
serde_json 的 Compound 也是七种复合类型共用一个,用的是三态的 State(Empty / First / Rest)。多出来的 Empty 是给 serialize_seq(Some(0)) 用的:长度为 0 时开头就写完 [],end 什么都不写。迷你版不看 len,[ 在开始时写、] 在 end 时写,空数组输出同样是 []。
struct 直接当 map 写:serialize_struct 转给 serialize_map,serialize_field(key, value) 就是 serialize_key(key) 加 serialize_value(value)。serde_json 的 SerializeStruct::serialize_field 也是转给 serialize_entry。
字符串转义
要和 serde_json 逐字节一致,转义规则必须完全相同。serde_json 查一张 256 字节的 ESCAPE 表:" 和 \ 转成 \"、\\;0x08、0x09、0x0A、0x0C、0x0D 转成 \b、\t、\n、\f、\r;其余小于 0x20 的字节写成 \u00XX,十六进制用小写(HEX_DIGITS 是 b"0123456789abcdef");0x7F、/ 和所有非 ASCII 字符原样输出。迷你版用一个 match 写同一张表:
fn write_escaped(out: &mut String, value: &str) {
out.push('"');
let mut start = 0;
for (i, byte) in value.bytes().enumerate() {
let short = match byte {
b'"' => b'"',
b'\\' => b'\\',
0x08 => b'b',
0x09 => b't',
0x0A => b'n',
0x0C => b'f',
0x0D => b'r',
0x00..=0x1F => b'u',
_ => continue,
};
// `i` is at an ASCII byte, so both slices end on char boundaries.
out.push_str(&value[start..i]);
out.push('\\');
out.push(char::from(short));
if short == b'u' {
out.push_str(&format!("{byte:04x}"));
}
start = i + 1;
}
out.push_str(&value[start..]);
out.push('"');
}
逐字节扫描、遇到要转义的字节才把前面那一段整段写出,这也是 serde_json format_escaped_str_contents 的做法(它把整段交给 write_string_fragment)。要转义的字节都小于 0x80,UTF-8 的多字节序列里不会出现这样的字节,所以切片的两端总在字符边界上,&value[start..i] 不会 panic。
键必须是字符串
JSON 对象的键只能是字符串。SerializeMap::serialize_key 不把键交给普通的 Serializer,而是交给 MapKeySerializer:字符串照写;i64 加上引号写成 "1";bool、seq、map、struct 报 key must be a string,这几个方法的状态对象类型填 Impossible<(), Error>。
impl ser::Serializer for MapKeySerializer<'_> {
// ...
type SerializeSeq = ser::Impossible<(), Error>;
fn serialize_i64(self, v: i64) -> Result<(), Error> {
self.ser.out.push('"');
self.ser.out.push_str(&v.to_string());
self.ser.out.push('"');
Ok(())
}
fn serialize_seq(self, _len: Option<usize>) -> Result<ser::Impossible<(), Error>, Error> {
Err(key_must_be_a_string())
}
// ...
}
serde_json 的 MapKeySerializer 是同一个思路,但收得更宽:整数、浮点数、char、unit 变体都能当键;bool 键从 1.0.105 起写成 "true" / "false"。下面的实验里,BTreeMap<bool, i64> 在迷你版报错,在 serde_json 1.0.154 里成功。哪些键会报错见 key must be a string。
derive(Serialize)
过程宏要单独一个 proc-macro = true 的 crate。依赖和 serde_derive 1.0.229 一样是 proc-macro2、quote、syn 3,syn 只开 derive、parsing、printing、proc-macro 四个 feature。
#[proc_macro_derive(Serialize)]
pub fn derive_serialize(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match expand(input) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
fn expand(mut input: DeriveInput) -> syn::Result<TokenStream2> {
let fields = match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(named) => &named.named,
// ... 元组 struct、unit struct 报错
},
// ... enum、union 报错
};
let ident = &input.ident;
let name = ident.to_string();
let len = fields.len();
let serialize_fields = fields.iter().filter_map(|field| {
let member = field.ident.as_ref()?;
let key = member.to_string();
Some(quote! {
::mini_serde::ser::SerializeStruct::serialize_field(&mut state, #key, &self.#member)?;
})
});
let body = quote! {
let mut state = ::mini_serde::Serializer::serialize_struct(serializer, #name, #len)?;
#(#serialize_fields)*
::mini_serde::ser::SerializeStruct::end(state)
};
for param in input.generics.type_params_mut() {
param.bounds.push(parse_quote!(::mini_serde::Serialize));
}
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
Ok(quote! {
impl #impl_generics ::mini_serde::Serialize for #ident #ty_generics #where_clause {
fn serialize<S: ::mini_serde::Serializer>(&self, serializer: S) -> ::core::result::Result<S::Ok, S::Error> {
#body
}
}
})
}
body 那三行就是 serde_derive serialize_struct_as_struct 里 quote_block! 的骨架。mini_serde 用 pub use mini_serde_derive::Serialize; 把宏和 trait 放在同一个名字下(宏和 trait 在不同的命名空间),和 serde 的做法一样,所以 compare 里可以写:
#[derive(serde::Serialize, mini_serde::Serialize, Debug, PartialEq)]
pub struct Post {
pub title: String,
pub tags: Vec<String>,
pub views: u32,
pub draft: bool,
pub votes: BTreeMap<String, i64>,
}
#[derive(serde::Serialize, mini_serde::Serialize)]
pub struct Page<T> {
pub items: Vec<T>,
pub total: i64,
}
两个 derive 实现的是不同的 trait,互不冲突。cargo expand --lib 展开出来的迷你版(Post 的五个字段只留两个):
impl ::mini_serde::Serialize for Post {
fn serialize<S: ::mini_serde::Serializer>(
&self,
serializer: S,
) -> ::core::result::Result<S::Ok, S::Error> {
let mut state = ::mini_serde::Serializer::serialize_struct(
serializer,
"Post",
5usize,
)?;
::mini_serde::ser::SerializeStruct::serialize_field(
&mut state,
"title",
&self.title,
)?;
// ... tags、views、draft
::mini_serde::ser::SerializeStruct::serialize_field(
&mut state,
"votes",
&self.votes,
)?;
::mini_serde::ser::SerializeStruct::end(state)
}
}
impl<T: ::mini_serde::Serialize> ::mini_serde::Serialize for Page<T> {
// ...
}
(这段是 cargo expand 用 rustfmt 排的版,所以是 4 空格。)用在 enum 上,syn::Error::new_spanned 把错误指到类型名上:
#[derive(mini_serde::Serialize)]
enum Status {
Draft,
Published(u32),
}
error: mini_serde: only structs with named fields
--> src/main.rs:2:6
|
2 | enum Status {
| ^^^^^^
error: could not compile `compile_fail` (bin "compile_fail") due to 1 previous error
和 serde_derive 比,迷你版省掉的东西都在 派生宏的流水线 和 生成的 Serialize 里讲过:
| serde_derive | 迷你版 | 迷你版的后果 |
|---|---|---|
wrap_in_const 包成 const _: () = { extern crate serde as _serde; ... },#[serde(crate = "...")] 把它换成 use 路径 as _serde | 直接写 ::mini_serde::... | 依赖改了名就找不到路径,也没有属性可以指定 |
字段数写成 false as usize + 1 + ...,skip_serializing_if 让它成为运行期值 | 字面量 5usize | 没有属性,字段数在编译期就定了 |
with_bound 只给字段里出现的类型参数加 bound | 每个类型参数都加 T: Serialize | PhantomData<T> 字段也要求 T: Serialize,见 泛型与 bound |
#[serde(...)] 属性、Ctxt 攒错误一起报 | 不声明 attributes(serde)、不读属性,第一个错误就返回 | 同时派生两边时,#[serde(rename = "x")] 只改 serde 那一份输出 |
Deserialize、Deserializer 与 Visitor
反序列化一侧的三个 trait(Deserialize、Deserializer 与 Visitor):
pub trait Deserialize<'de>: Sized {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>;
}
pub trait Deserializer<'de>: Sized {
type Error: Error;
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error>;
fn deserialize_bool<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_any(visitor)
}
// ... deserialize_i64、deserialize_str、deserialize_seq、deserialize_map 同样转给 any
fn deserialize_struct<V: Visitor<'de>>(
self,
_name: &'static str,
_fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error> {
self.deserialize_map(visitor)
}
}
pub trait Visitor<'de>: Sized {
type Value;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result;
fn visit_bool<E: Error>(self, v: bool) -> Result<Self::Value, E> {
Err(E::invalid_type(Unexpected::Bool(v), &self))
}
// ... visit_i64、visit_str 同样默认拒绝
fn visit_borrowed_str<E: Error>(self, v: &'de str) -> Result<Self::Value, E> {
self.visit_str(v)
}
fn visit_seq<A: SeqAccess<'de>>(self, _seq: A) -> Result<Self::Value, A::Error> {
Err(Error::invalid_type(Unexpected::Seq, &self))
}
// ... visit_map
}
和 serde 有一处有意的不同:迷你版的提示方法都有默认实现,转给 deserialize_any。serde 的 Deserializer 31 个 deserialize_* 只有 deserialize_i128 / deserialize_u128 有默认实现,每个格式都得自己决定每个提示怎么读:bincode 必须照提示读,自描述格式想全转给 any 时用 forward_to_deserialize_any! 显式写出来。迷你版只有 JSON 一个格式,默认实现相当于替它写好了这个宏。
Visitor 的默认实现照抄 serde:类型只覆盖它接受的 visit_*,其余一律 invalid_type。报错要说「本来想要什么」,这句话由 expecting 提供。serde 的做法是给所有 Visitor 一个 Expected 的 blanket impl,再给 dyn Expected 实现 Display,迷你版逐行照搬:
impl<'de, V: Visitor<'de>> Expected for V {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.expecting(f)
}
}
impl Display for dyn Expected + '_ {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Expected::fmt(self, f)
}
}
pub trait Error: Sized + std::error::Error {
fn custom<T: Display>(msg: T) -> Self;
fn invalid_type(unexp: Unexpected, exp: &dyn Expected) -> Self {
Self::custom(format_args!("invalid type: {unexp}, expected {exp}"))
}
// ... missing_field
}
Unexpected 的 Display 也按 serde 的措辞写(boolean `true`、integer `1`、string "..."),下面的实验里迷你版和 serde_json 的报错前半句因此一字不差。错误怎么拼出来见 错误。
借用:&'de str 只接 visit_borrowed_str
visit_borrowed_str 默认转给 visit_str,所以 String 只覆盖 visit_str 就能同时收下两种字符串。&'a str 反过来,只覆盖 visit_borrowed_str,visit_str 保持默认的拒绝,因为 visit_str 拿到的 &str 活不过这次调用,变不成 &'de str:
struct BorrowedStrVisitor;
impl<'de> Visitor<'de> for BorrowedStrVisitor {
type Value = &'de str;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a borrowed string")
}
fn visit_borrowed_str<E: Error>(self, v: &'de str) -> Result<&'de str, E> {
Ok(v)
}
}
impl<'de: 'a, 'a> Deserialize<'de> for &'a str {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_str(BorrowedStrVisitor)
}
}
serde 的 impl Deserialize for &'a str 是同一个签名,expecting 也是 a borrowed string。能不能借,由格式调哪个方法决定,见 生命周期 'de 与借用。
SeqAccess 与 MapAccess
pub trait SeqAccess<'de> {
type Error: Error;
fn next_element<T: Deserialize<'de>>(&mut self) -> Result<Option<T>, Self::Error>;
}
pub trait MapAccess<'de> {
type Error: Error;
fn next_key<K: Deserialize<'de>>(&mut self) -> Result<Option<K>, Self::Error>;
fn next_value<V: Deserialize<'de>>(&mut self) -> Result<V, Self::Error>;
}
serde 的 SeqAccess / MapAccess 里必须实现的是 next_element_seed / next_key_seed / next_value_seed,next_element 这些是用 PhantomData 当 seed 的默认实现。迷你版没有 DeserializeSeed,直接把带类型参数的版本作为必选方法。代价是值的反序列化不能带状态(SeqAccess、MapAccess 与 EnumAccess 里「把二维数组读成一维」那种写法做不了)。
JSON 解析器
mini_json::Deserializer<'de> 是输入 &'de str、一个下标和一块 scratch: String。serde_json 的 Deserializer 多一层 Read(StrRead、SliceRead、IoRead),还有递归深度计数(反序列化一侧)。
deserialize_any:看首字节
JSON 自描述,看一个字节就知道下面是什么:
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Error> {
let peek = self.peek();
let at = self.index;
let result = match peek {
None => return Err(Error::at("EOF while parsing a value", self.index)),
Some(b't') => {
self.parse_literal("true")?;
visitor.visit_bool(true)
}
// ... b'f' 同理
Some(b'-' | b'0'..=b'9') => {
let v = self.parse_i64()?;
visitor.visit_i64(v)
}
Some(b'"') => {
self.index += 1;
match self.parse_str()? {
Reference::Borrowed(s) => visitor.visit_borrowed_str(s),
Reference::Copied(s) => visitor.visit_str(s),
}
}
Some(b'[') => {
self.index += 1;
let value = visitor.visit_seq(Access { de: &mut *self, first: true })?;
self.consume(b']')?;
Ok(value)
}
// ... b'{' 同理,调 visit_map,最后要 `}`
Some(b'n') => Err(Error::at("mini_json: null is not in the data model", self.index)),
Some(_) => Err(Error::at("expected value", self.index)),
};
// Errors made by the visitor have no position; give them the value's.
result.map_err(|err| err.with_offset(at))
}
最后一行对应 serde_json deserialize_any 末尾的 fix_position。Visitor 报的错是经 de::Error::custom 造出来的,custom 不知道自己在输入的哪里;格式在 visit_* 返回之后补上位置。serde_json 那里的注释写着「The de::Error impl creates errors with unknown line and column.」。迷你版的 Error 里位置是 Option<usize>,只有还是 None 时才补。
from_str 在值读完之后看剩下的是不是只有空白,和 serde_json 的 end 一样,否则 "true false" 会被当成 true 接受。
parse_str:能借就借
enum Reference<'de, 's> {
Borrowed(&'de str),
Copied(&'s str),
}
fn parse_str(&mut self) -> Result<Reference<'de, '_>, Error> {
let input: &'de str = self.input;
let bytes = input.as_bytes();
self.scratch.clear();
let mut start = self.index;
loop {
let Some(&byte) = bytes.get(self.index) else {
return Err(Error::at("EOF while parsing a string", self.index));
};
match byte {
b'"' => {
let run = &input[start..self.index];
self.index += 1;
if self.scratch.is_empty() {
return Ok(Reference::Borrowed(run));
}
self.scratch.push_str(run);
return Ok(Reference::Copied(&self.scratch));
}
b'\\' => {
self.scratch.push_str(&input[start..self.index]);
self.index += 1;
self.parse_escape()?;
start = self.index;
}
0x00..=0x1F => {
return Err(Error::at("control character while parsing a string", self.index));
}
_ => self.index += 1,
}
}
}
两个生命周期是这个函数的关键:Borrowed 里的切片来自 input,活得和输入一样长('de);Copied 里的是 scratch 的借用,活到下一次 &mut self 调用为止('s)。函数开头把 self.input 拷进局部变量 input: &'de str,切片就不再经过 &mut self,编译器才肯把它当成 'de 返回。serde_json 的 Reference 是同样的两个变体,SliceRead 的 parse_str_bytes 判断方式也一样:到达结尾引号时 scratch 还是空的,就说明一路没遇到转义,直接返回输入的切片:
match self.slice[self.index] {
b'"' => {
if scratch.is_empty() {
// Fast path: return a slice of the raw JSON without any
// copying.
let borrowed = &self.slice[start..self.index];
self.index += 1;
return result(self, borrowed).map(Reference::Borrowed);
} else {
scratch.extend_from_slice(&self.slice[start..self.index]);
self.index += 1;
return result(self, scratch).map(Reference::Copied);
}
}
差别是 serde_json 先用 skip_to_escape 一次跳过 8 个字节,迷你版一个字节一个字节地走。parse_escape 处理 \"、\\、\/、\b、\f、\n、\r、\t 和 \uXXXX,\uD83E\uDD80 这样的代理对合成一个 U+10000 以上的字符,单独的前导代理报错。
整数在负数一侧累加(checked_mul(10) 再 checked_sub(digit)),最后非负数再取反,这样 -9223372036854775808 也放得下;溢出、前导零、单独一个 - 都报错,遇到 .、e 直接报「只支持整数」。serde_json 是先按 u64 累加,放不下再转成浮点(反序列化一侧)。
Access:一个 struct 实现两个 trait
[...] 和 {...} 的区别只在结束符和键值之间的 :。一个 Access 同时实现 SeqAccess 和 MapAccess,has_next 管逗号:
fn has_next(&mut self, close: u8) -> Result<bool, Error> {
match self.de.peek() {
Some(byte) if byte == close => return Ok(false),
Some(b',') if !self.first => self.de.index += 1,
Some(_) if self.first => {}
Some(_) => return Err(Error::at("expected `,`", self.de.index)),
None => return Err(Error::at("EOF while parsing a value", self.de.index)),
}
self.first = false;
Ok(true)
}
结束符留给 deserialize_any 在 visit_seq / visit_map 返回后去吃。这样 Visitor 不读完所有元素就返回时,consume(b']') 会报错,而不是把剩下的元素留给后面的解析。[1,] 会在逗号之后的 ] 上失败:next_element 已经越过逗号,T::deserialize 看到 ] 报 expected value。next_key 先检查下一个字节是不是 ",否则报 key must be a string。
compare/src/bin/edge.rs 里 First 的 visit_seq 只取一个元素就返回,Nest 是任意深度的 [[[...]]]:
for input in ["[1,]", "[1 2]"] {
println!("{input}: {:?}", mini_json::from_str::<Vec<i64>>(input).map_err(|e| e.to_string()));
}
println!("[1,2] as First: {:?}", mini_json::from_str::<First>("[1,2]").map(|f| f.0).map_err(|e| e.to_string()));
// ... 深度从命令行读,默认 1000
println!("depth {depth}: ok = {}", mini_json::from_str::<Nest>(&input).is_ok());
[1,]: Err("expected value at byte 3")
[1 2]: Err("expected `,` at byte 3")
[1,2] as First: Err("expected `]` at byte 2")
depth 1000: ok = true
深度传 1000000 时(debug 构建)进程打印 thread 'main' (2808718) has overflowed its stack 和 fatal runtime error: stack overflow, aborting 后退出。迷你版没有 serde_json 那样的深度计数。
MapKeySerializer 把 BTreeMap<i64, bool> 的键 1 写成 "1"。用 mini_json::from_str::<BTreeMap<i64, bool>> 读这段输出,结果是 Ok 还是 Err?serde_json 读同一段呢?
let map = BTreeMap::from([(1_i64, true)]);
let json = mini_json::to_string(&map)?; // {"1":true}
mini_json::from_str::<BTreeMap<i64, bool>>(&json); // ?
serde_json::from_str::<BTreeMap<i64, bool>>(&json); // ?
手写 Post 的 Deserialize
derive Deserialize 不在本页范围,Post 的 Deserialize 手写。它是 生成的 Deserialize 的缩水版:没有 __Field 枚举,键直接读成 String 再 match;每个字段一个 Option,循环结束后缺的报 missing_field。
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Post, A::Error> {
let mut title = None;
let mut tags = None;
let mut views: Option<i64> = None;
let mut draft = None;
let mut votes = None;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"title" => title = Some(map.next_value()?),
"tags" => tags = Some(map.next_value()?),
"views" => views = Some(map.next_value()?),
"draft" => draft = Some(map.next_value()?),
"votes" => votes = Some(map.next_value()?),
other => {
return Err(de::Error::custom(format_args!("unknown field `{other}`")));
}
}
}
let views = views.ok_or_else(|| de::Error::missing_field("views"))?;
Ok(Post {
title: title.ok_or_else(|| de::Error::missing_field("title"))?,
// ... tags、draft、votes 同样
views: u32::try_from(views).map_err(|_| de::Error::custom("views out of range"))?,
})
}
键读成 String 每个键都要分配一次,derive 生成的 __FieldVisitor 在 visit_str 里直接比较,不分配。views 先读成 i64 再检查范围,因为数据模型里只有 i64;serde 的 u32 有自己的 Visitor,在 visit_i64 / visit_u64 里查范围(标准库类型的实现)。重复的键在这里后一个覆盖前一个,生成的代码会报 duplicate_field。
和 serde_json 对比
compare/src/bin/compare.rs:同一个 Post 分别交给两个写出器,比较字符串;再用迷你解析器读 serde_json 的输出;最后把 U+0000 到 U+10FFFF 的全部标量值(去掉代理区)拼成一个字符串,两边各写一次。
fn main() -> Result<(), Box<dyn Error>> {
let post = sample();
let mine = mini_json::to_string(&post)?;
let real = serde_json::to_string(&post)?;
println!("mini_json : {mine}");
println!("serde_json: {real}");
println!("byte-identical: {}", mine == real);
let back: Post = mini_json::from_str(&real)?;
println!("mini_json::from_str(serde_json output) == post: {}", back == post);
let page = Page { items: vec![sample(), sample()], total: 2 };
let mine = mini_json::to_string(&page)?;
let real = serde_json::to_string(&page)?;
println!("Page<Post>: {} bytes, byte-identical: {}", mine.len(), mine == real);
// Every char from U+0000 to U+10FFFF (minus surrogates) in one string.
let all: String = (0..=0x10FFFF_u32).filter_map(char::from_u32).collect();
let mine = mini_json::to_string(&all)?;
let real = serde_json::to_string(&all)?;
println!("all {} chars: byte-identical: {}", all.chars().count(), mine == real);
let back: String = mini_json::from_str(&real)?;
println!("all chars round-trip: {}", back == all);
let escaped: String = mini_json::from_str(r#""\u4e2d\ud83e\udd80\/""#)?;
println!("escapes: {escaped}");
let real: String = serde_json::from_str(r#""\u4e2d\ud83e\udd80\/""#)?;
println!("serde_json agrees: {}", escaped == real);
Ok(())
}
sample() 的标题是 "mini \"serde\"\n—写一个",带引号、换行和非 ASCII 字符。M2 Max、macOS、rustc 1.99.0:
mini_json : {"title":"mini \"serde\"\n—写一个","tags":["rust","json"],"views":2026,"draft":false,"votes":{"down":-3,"up":42}}
serde_json: {"title":"mini \"serde\"\n—写一个","tags":["rust","json"],"views":2026,"draft":false,"votes":{"down":-3,"up":42}}
byte-identical: true
mini_json::from_str(serde_json output) == post: true
Page<Post>: 259 bytes, byte-identical: true
all 1112064 chars: byte-identical: true
all chars round-trip: true
escapes: 中🦀/
serde_json agrees: true
在这个数据模型能表示的范围内,输出逐字节相同。views: u32 在 serde_json 里走 serialize_u32,在迷你版里走 serialize_i64,写出来都是 2026。
借用与报错
compare/src/bin/borrow.rs 把同样的输入交给两个解析器,看借用和报错:
for input in [r#""plain""#, r#""with \"escape\"""#] {
let mine: Result<&str, _> = mini_json::from_str(input);
let real: Result<&str, _> = serde_json::from_str(input);
// ... 打印两边结果;mini_json 成功时检查切片指针是否落在 input 里
}
// ... 再对四段输入分别读成 BTreeMap<String, String>、Vec<bool>、BTreeMap<String, i64>、i64
"plain"
mini_json : Ok("plain") borrowed: true
serde_json: Ok("plain")
"with \"escape\""
mini_json : Err(invalid type: string "with \"escape\"", expected a borrowed string at byte 0)
serde_json: Err(invalid type: string "with \"escape\"", expected a borrowed string at line 1 column 17)
{"title": 1} as BTreeMap<String, String>
mini_json : invalid type: integer `1`, expected a string at byte 10
serde_json: invalid type: integer `1`, expected a string at line 1 column 11
[true, 7, "x"] as Vec<bool>
mini_json : invalid type: integer `7`, expected a boolean at byte 7
serde_json: invalid type: integer `7`, expected a boolean at line 1 column 8
{"a": 1, } as BTreeMap<String, i64>
mini_json : key must be a string at byte 9
serde_json: trailing comma at line 1 column 10
9223372036854775808 as i64
mini_json : number out of range at byte 0
serde_json: invalid value: integer `9223372036854775808`, expected i64 at line 1 column 19
逐条看:
- 有转义的字符串读成
&str,两边都在visit_str的默认实现里失败,前半句一样。这是invalid type: string ..., expected a borrowed string的原因。 - 位置不同:迷你版报值开头的字节偏移(0 起),serde_json 报行列号(1 起),列指向值的最后一个字符,因为它在值读完之后才
fix_position。一位数的1、7开头和结尾是同一个字符,所以 byte 10 对 column 11、byte 7 对 column 8;带转义的字符串从 byte 0 开始,serde_json 报的 column 17 是它的闭引号。 - 尾逗号:serde_json 在
MapAccess里专门检查逗号后面是不是},给出trailing comma;迷你版在逗号后直接要求",报的是key must be a string。 - 超出 i64 的整数:serde_json 先按 u64 读出
9223372036854775808,交给i64的Visitor的visit_u64,由Visitor报invalid value;迷你版在解析器里就溢出了,Visitor没有机会说话。
没写的部分
| 没写 | 迷你版的表现 | serde 里讲它的页 |
|---|---|---|
null、Option、unit | null 直接报错 | 标准库类型的实现 |
| 浮点数、u64、i128 | 1.5 报错,u64 没有 Serialize | 反序列化一侧 |
enum 和 EnumAccess | derive 拒绝 enum | 外部标签 |
DeserializeSeed | Access 只收 T: Deserialize | SeqAccess、MapAccess 与 EnumAccess |
Content 缓冲 | 没有 untagged、内部标签、flatten | Content:缓冲的代价 |
| 递归深度限制 | 100 万层的 [[[...]]] 栈溢出 | 递归上限 |
| 提示 | 全部转给 deserialize_any,换成二进制格式就不成立 | Deserialize、Deserializer 与 Visitor |
Formatter、缩进 | 只有紧凑输出 | 序列化一侧 |
io::Write / io::Read | 只写 String、只读 &str | 反序列化一侧 |
测试在 mini_json/tests/json.rs,8 个:derive 具名 struct 和泛型 struct、转义、整数键与非法键、i64 两端和非法数字、带空白的嵌套、借用与转义、10 段非法 JSON 都报错。
derive Deserialize、enum 的几种表示和练习题放在课程里。
动脑筋答案
compare/src/bin/int_keys.rs,另外把 BTreeMap<bool, i64> 交给两个写出器:
mini_json::to_string : {"1":true}
mini_json::from_str : Err(invalid type: string "1", expected i64 at byte 1)
serde_json::from_str : Ok({1: true})
bool key, mini_json : Err("key must be a string")
bool key, serde_json : Ok("{\"true\":1}")
- 迷你版写得出、读不回。
next_key::<i64>()把键交给普通的deserialize_any,它看到"就调visit_borrowed_str("1");i64的Visitor只覆盖了visit_i64,visit_str走默认实现报invalid type。位置是 byte 1,键的开引号。 - serde_json 读得回,因为它的
MapAccess::next_key_seed不把键交给主Deserializer,而是交给MapKey。MapKey的deserialize_i64由deserialize_numeric_key!生成:吃掉开引号,按数字解析,再要求一个闭引号。写出一侧有MapKeySerializer,读回一侧就要有对应的MapKey,两边接受的类型要对上。迷你版只写了前一半。 - bool 键:迷你版的
MapKeySerializer::serialize_bool报错,serde_json 1.0.154 写成"true"。
数字键能写出是 0.8.5 就有的(「Allow integers to be used as map keys (#177)」)。到 1.0.0,写出一侧还是 {"1":"one"},读回一侧却不认了,和上面的迷你版一样。#177 下有人留言「Did we regress this between 0.9 and 1.0?」。修复是 #311(「Allow integers to be used as map keys again」),PR 描述是「deserializing map keys with a special Deserializer that allows the same types that the map key Serializer allows」,也就是今天的 MapKey。它在 1.0.0 发布 8 天后进了 1.0.1,发布说明写着「Fix deserialization of maps with non-string keys (#311)」和「For example {"1":"one"} can be serialized and deserialized as BTreeMap<i32, String>.」。bool 键的写出和读回是 2023-08-15 的两个提交,「Support serializing bool in map keys」和「Support deserializing bool in map keys」,一起进了 1.0.105。
小结
- 迷你版保留了 serde 的骨架:
Serializer按值拿self,复合类型是「开始、逐个元素、end」的状态对象;Visitor默认全部拒绝,Expected的 blanket impl 让报错能说出「本来想要什么」。 - JSON 写出器只需要一个
first: bool决定逗号;转义按 serde_json 的ESCAPE表写,全部 1 112 064 个标量值的输出逐字节一致。 - 解析器按首字节调
visit_*;parse_str在没有转义时返回Reference::Borrowed,&'de str只接这一种。Visitor的错误由格式在返回后补上位置。 #[derive(Serialize)]64 行,展开结果是serialize_struct、逐字段serialize_field、end;serde_derive 多出来的是const _包装、按字段推断的 bound、属性和攒错误。- 写出和读回要成对:迷你版能把 i64 键写成
"1",却读不回;serde_json 靠MapKey解决,1.0.1 才补上。