外部标签
主线位置:反序列化主线上 enum 的默认路径,没有缓冲。生成的
Deserialize::deserialize调deserialize_enum("Shape", VARIANTS, __Visitor),serde_json 看第一个字节,交出EnumAccess;__Visitor::visit_enum先用variant_seed读出变体标识__Field,再按变体形状调VariantAccess的四个方法之一,内容直接从 JSON 里读。序列化一侧是对应的serialize_*_variant。
sequenceDiagram participant J as serde_json participant V as Shape 的 Visitor participant A as VariantAccess participant F as __Field J->>V: visit_enum(A) V->>A: variant_seed A->>F: deserialize_identifier F-->>V: __field3 V->>A: struct_variant(FIELDS) A->>V: visit_map 或 visit_seq V-->>J: Shape::Rect J->>J: 检查右花括号
EnumAccess / VariantAccess 这两个 trait 的定义、serde_json 两个实现的分工、"Circle" 撞上 UnitVariantAccess 报什么错,在 SeqAccess、MapAccess 与 EnumAccess 里已经讲过;这一页讲 derive 生成的那一半,以及外部标签特有的几件事:变体标识怎么认、多出来的键报什么、rename / alias / #[serde(other)] 落在哪里。生成代码的源码在 de/enum_externally.rs,213 行。实验在 M2 Max、macOS、rustc 1.99.0 上跑,依赖 serde = "=1.0.229"(derive)、serde_json = "=1.0.154"、bincode = "=1.3.3"。
本页的例子(src/bin/ext.rs):
#[derive(Serialize, Deserialize, Debug)]
enum Shape {
Dot,
Circle(f64),
Pair(u8, u8),
#[serde(rename = "rect", alias = "box")]
Rect { w: u32, h: u32 },
}
序列化:变体名作键
生成的 Serialize 对四种变体各调一个方法,带上类型名、变体下标(0u32 到 3u32)和变体名:
_serde::Serializer::serialize_unit_variant(
__serializer,
"Shape",
0u32,
"Dot",
)
// ...
_serde::Serializer::serialize_newtype_variant(
__serializer,
"Shape",
1u32,
"Circle",
__field0,
)
// ...
let mut __serde_state = _serde::Serializer::serialize_struct_variant(
__serializer,
"Shape",
3u32,
"rect",
0 + 1 + 1,
)?;
变体名已经是 rename 之后的 "rect";alias 只影响反序列化。外部标签就是 serde 数据模型对 enum 的原生表示:「变体名 + 内容」,不加任何约定,怎么落到字节上完全由格式决定。serde_json 的 serialize_unit_variant 只写名字:
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<()> {
self.serialize_str(variant)
}
serialize_newtype_variant 用 Formatter 的 begin_object、begin_object_key 等钩子写出 {"Circle":,再递归写内容,最后 end_object。tuple 和 struct 变体同理,外面多包一层对象。src/bin/ext.rs ser 用一次往返里打印调用的 LogSer 和 LogWriter 跑前两个变体:
serialize_unit_variant("Shape", 0, "Dot")
write "\""
write "Dot"
write "\""
output: "Dot"
serialize_newtype_variant("Shape", 1, "Circle")
write "{"
write "\""
write "Circle"
write "\""
write ":"
serialize_f64(1.5)
write "1.5"
write "}"
output: {"Circle":1.5}
serde_json 不写变体下标;bincode 只写下标(u32),不写名字,后面 #[serde(other)] 一节会用到这一点。四种 serialize_*_variant 在 derive 里怎么生成,在生成的 Serialize。
生成的 Deserialize
enum_externally::deserialize 生成的整体结构只有四块:
quote_block! {
#variant_visitor
#[doc(hidden)]
struct __Visitor #de_impl_generics #where_clause {
marker: _serde::#private::PhantomData<#this_type #ty_generics>,
lifetime: _serde::#private::PhantomData<&#delife ()>,
}
#[automatically_derived]
impl #de_impl_generics _serde::de::Visitor<#delife> for __Visitor #de_ty_generics #where_clause {
type Value = #this_type #ty_generics;
fn expecting(&self, __formatter: &mut _serde::#private::Formatter) -> _serde::#private::fmt::Result {
_serde::#private::Formatter::write_str(__formatter, #expecting)
}
fn visit_enum<__A>(self, __data: __A) -> _serde::#private::Result<Self::Value, __A::Error>
where
__A: _serde::de::EnumAccess<#delife>,
{
#match_variant
}
}
#variants_stmt
_serde::Deserializer::deserialize_enum(
__deserializer,
#type_name,
VARIANTS,
__Visitor {
marker: _serde::#private::PhantomData::<#this_type #ty_generics>,
lifetime: _serde::#private::PhantomData,
},
)
}
#variant_visitor 是变体标识 __Field 和它的 __FieldVisitor,#variants_stmt 是 const VARIANTS,两者都由 enum_.rs 的 prepare_enum_variant_enum 生成,内部标签和相邻标签也用它。__Visitor 只实现 visit_enum;JSON 里给一个数组或数字,serde_json 在 deserialize_enum 里就先报错了,轮不到 Visitor。#de_impl_generics 这些泛型片段在泛型与 bound。
变体标识 __Field
变体标识和结构体的字段标识是同一个生成器(identifier::deserialize_generated,is_variant = true),visit_str / visit_bytes / visit_u64 三种认法的来历在生成的 Deserialize。Shape 展开后:
enum __Field {
__field0,
__field1,
__field2,
__field3,
}
// ...
fn visit_u64<__E>(
self,
__value: u64,
) -> _serde::__private229::Result<Self::Value, __E>
where
__E: _serde::de::Error,
{
match __value {
0u64 => _serde::__private229::Ok(__Field::__field0),
1u64 => _serde::__private229::Ok(__Field::__field1),
2u64 => _serde::__private229::Ok(__Field::__field2),
3u64 => _serde::__private229::Ok(__Field::__field3),
_ => {
_serde::__private229::Err(
_serde::de::Error::invalid_value(
_serde::de::Unexpected::Unsigned(__value),
&"variant index 0 <= i < 4",
),
)
}
}
}
fn visit_str<__E>(
self,
__value: &str,
) -> _serde::__private229::Result<Self::Value, __E>
where
__E: _serde::de::Error,
{
match __value {
"Dot" => _serde::__private229::Ok(__Field::__field0),
"Circle" => _serde::__private229::Ok(__Field::__field1),
"Pair" => _serde::__private229::Ok(__Field::__field2),
"box" => _serde::__private229::Ok(__Field::__field3),
"rect" => _serde::__private229::Ok(__Field::__field3),
_ => {
_serde::__private229::Err(
_serde::de::Error::unknown_variant(__value, VARIANTS),
)
}
}
}
// ...
const VARIANTS: &'static [&'static str] = &[
"Dot",
"Circle",
"Pair",
"box",
"rect",
];
和字段标识比,有三处不同:
- 没有
__ignore。 结构体遇到不认识的字段默认跳过(Rect自己的字段标识里就有_ => Ok(__Field::__ignore));变体标识不认识就是错,deserialize_identifier的兜底分支在is_variant时是unknown_variant(__value, VARIANTS),字段是unknown_field(__value, FIELDS)。 - 下标越界的错误信息。
"variant index 0 <= i < 4",字段是"field index ..."。JSON 用不到这个分支,bincode 这类按下标写变体的格式会走到。 VARIANTS里是反序列化时接受的全部名字。 每个变体的aliases()是一个BTreeSet<Name>,包括主名字本身,按字符串排序,所以Rect贡献了"box"、"rect"两项,"Rect"不在其中。这个数组传给deserialize_enum,也出现在unknown variant的报错里。
visit_enum:一个 match
#match_variant 展开后是一个 match,每个变体一条分支:
match _serde::de::EnumAccess::variant(__data) {
_serde::__private229::Ok((__Field::__field0, __variant)) => {
_serde::de::VariantAccess::unit_variant(__variant)?;
_serde::__private229::Ok(Shape::Dot)
}
_serde::__private229::Ok((__Field::__field1, __variant)) => {
_serde::__private229::Result::map(
_serde::de::VariantAccess::newtype_variant::<
f64,
>(__variant),
Shape::Circle,
)
}
_serde::__private229::Ok((__Field::__field2, __variant)) => {
// ... 一个局部的 __Visitor,只实现 visit_seq
_serde::de::VariantAccess::tuple_variant(
__variant,
2usize,
__Visitor { /* ... */ },
)
}
_serde::__private229::Ok((__Field::__field3, __variant)) => {
// ... 局部的 __Field(w、h、__ignore)和 __Visitor(visit_seq、visit_map)
const FIELDS: &'static [&'static str] = &["w", "h"];
_serde::de::VariantAccess::struct_variant(
__variant,
FIELDS,
__Visitor { /* ... */ },
)
}
_serde::__private229::Err(__err) => {
_serde::__private229::Err(__err)
}
}
(花括号里的 /* ... */ 是本文删掉的 marker / lifetime 两个字段。)每条分支由 deserialize_externally_tagged_variant 按变体形状生成:
| 变体 | 调用 | 内容怎么读 |
|---|---|---|
Dot | unit_variant(__variant)? | 格式确认没有内容(或内容是 unit) |
Circle(f64) | newtype_variant::<f64>(__variant) | 直接 f64::deserialize |
Pair(u8, u8) | tuple_variant(__variant, 2, __Visitor) | 局部 __Visitor::visit_seq,和 tuple struct 的生成器相同(TupleForm::ExternallyTagged) |
Rect { w, h } | struct_variant(__variant, FIELDS, __Visitor) | 局部 __Visitor 的 visit_seq / visit_map,和 struct 的生成器相同(StructForm::ExternallyTagged) |
tuple 和 struct 变体的内容就是一个匿名的 tuple struct / struct,所以直接复用 tuple::deserialize 和 struct_::deserialize,只是外层的入口从 deserialize_tuple_struct / deserialize_struct 换成了 VariantAccess 的方法。newtype_variant 加了 skip_deserializing 的字段时改调 unit_variant 再填默认值;变体或字段写了 deserialize_with 时套一个 __DeserializeWith 包装类型。最后那条 Err(__err) => Err(__err) 是 1.0.229 把 variant(__data)? 改掉的结果,见怎么读这一卷。
enum 一个变体都没有(或全部 skip_deserializing)时,match_variant 换成 Result::map(EnumAccess::variant::<__Field>(__data), |(__impossible, _)| match __impossible {}):变体标识照样读,读成功了也不可能有值,所以对任何输入都报错。
serde_json 一侧:一个键,一个值
serde_json 的 deserialize_enum:
match tri!(self.parse_whitespace()) {
Some(b'{') => {
check_recursion! {
self.eat_char();
let ret = visitor.visit_enum(VariantAccess::new(self));
}
let value = tri!(ret);
match tri!(self.parse_whitespace()) {
Some(b'}') => {
self.eat_char();
Ok(value)
}
Some(_) => Err(self.error(ErrorCode::ExpectedSomeValue)),
None => Err(self.error(ErrorCode::EofWhileParsingObject)),
}
}
Some(b'"') => visitor.visit_enum(UnitVariantAccess::new(self)),
Some(_) => Err(self.peek_error(ErrorCode::ExpectedSomeValue)),
None => Err(self.peek_error(ErrorCode::EofWhileParsingValue)),
}
{ 开头时,VariantAccess 的 variant_seed 读一个键、吃掉冒号,四个方法读冒号后面的那一个值;visit_enum 返回后,这里要求紧接着就是 }。" 开头时交出 UnitVariantAccess,只有 unit_variant 能成功。别的首字节一律 expected value。所以「外部标签」在 serde_json 里的确切含义是:一个字符串,或者恰好只有一个键的对象。src/bin/ext.rs ok 和 err:
// "ok"
json::<Shape>(r#""Dot""#);
json::<Shape>(r#"{"Circle":1.5}"#);
json::<Shape>(r#"{"Pair":[1,2]}"#);
json::<Shape>(r#"{"rect":{"w":3,"h":4}}"#);
json::<Shape>(r#"{"box":{"w":3,"h":4}}"#);
json::<Shape>(r#"{"rect":[3,4]}"#);
// "err"
json::<Shape>(r#"{"Rect":{"w":3,"h":4}}"#);
json::<Shape>(r#""Hexagon""#);
json::<Shape>(r#"{"Dot":null,"Circle":1}"#);
json::<Shape>(r#"{}"#);
json::<Shape>(r#"["Dot"]"#);
json::<Shape>(r#"0"#);
json::<Shape>(r#"{"Pair":[1,2,3]}"#);
"Dot" => Ok(Dot)
{"Circle":1.5} => Ok(Circle(1.5))
{"Pair":[1,2]} => Ok(Pair(1, 2))
{"rect":{"w":3,"h":4}} => Ok(Rect { w: 3, h: 4 })
{"box":{"w":3,"h":4}} => Ok(Rect { w: 3, h: 4 })
{"rect":[3,4]} => Ok(Rect { w: 3, h: 4 })
{"Rect":{"w":3,"h":4}} => Err(unknown variant `Rect`, expected one of `Dot`, `Circle`, `Pair`, `box`, `rect` at line 1 column 7)
"Hexagon" => Err(unknown variant `Hexagon`, expected one of `Dot`, `Circle`, `Pair`, `box`, `rect` at line 1 column 9)
{"Dot":null,"Circle":1} => Err(expected value at line 1 column 11)
{} => Err(expected value at line 1 column 2)
["Dot"] => Err(expected value at line 1 column 1)
0 => Err(expected value at line 1 column 1)
{"Pair":[1,2,3]} => Err(trailing characters at line 1 column 14)
rename之后原名Rect就不认了,alias加的box认。{"rect":[3,4]}也能读:struct_variant在 serde_json 里是deserialize_struct(self.de, "", fields, visitor),而 serde_json 的deserialize_struct同时接受对象和数组。- 多一个键:
{"Dot":null读完,visit_enum已经返回Ok(Dot),再往后看到的是,而不是},报expected value,位置是第 11 列的逗号。这句话是ErrorCode::ExpectedSomeValue的文字,说的并不是「多了一个键」,排查时要靠列号。空对象{}在variant_seed里看到},报的也是它。 {"Pair":[1,2,3]}报trailing characters:tuple 变体的visit_seq取了两个元素就返回,serde_json 的end_seq发现]之前还有,3。
从 serde_json::Value 读(from_value)走的是另一个 Deserializer。Map 的 deserialize_enum 先数键的个数,不是一个就报错,信息更直白(src/bin/ext.rs value):
from_value {"Dot":null,"Circle":1} => Err(Error("invalid value: map, expected map with a single key", line: 0, column: 0))
from_value {} => Err(Error("invalid value: map, expected map with a single key", line: 0, column: 0))
不缓冲:值直接从解析器来
外部标签的标签在内容之前,读到键就知道是哪个变体,后面的内容可以直接交给对应变体的 Visitor。用打印包装 LogDe 套住 serde_json 的 Deserializer(src/bin/ext_trace.rs,同样的 Shape 去掉了 Pair):
for input in [r#"{"box":{"w":3,"h":4}}"#, r#"{"rect":[3,4]}"#] {
println!("-- {input}");
let mut de = serde_json::Deserializer::from_str(input);
let v = Shape::deserialize(LogDe(&mut de));
println!("=> {v:?}");
}
-- {"box":{"w":3,"h":4}}
deserialize_enum("Shape", ["Dot", "Circle", "box", "rect"])
visit_enum
variant_seed
deserialize_identifier
visit_borrowed_str("box")
struct_variant(["w", "h"])
visit_map
next_key_seed
deserialize_identifier
visit_borrowed_str("w")
next_value_seed
deserialize_u32
visit_u64(3)
next_key_seed
deserialize_identifier
visit_borrowed_str("h")
next_value_seed
deserialize_u32
visit_u64(4)
next_key_seed
=> None
=> Ok(Rect { w: 3, h: 4 })
-- {"rect":[3,4]}
deserialize_enum("Shape", ["Dot", "Circle", "box", "rect"])
visit_enum
variant_seed
deserialize_identifier
visit_borrowed_str("rect")
struct_variant(["w", "h"])
visit_seq
next_element_seed
deserialize_u32
visit_u64(3)
next_element_seed
deserialize_u32
visit_u64(4)
=> Ok(Rect { w: 3, h: 4 })
和本部分索引页里另外三种表示的调用序列对照,要看的是叶子:w 和 h 都是 deserialize_u32 → visit_u64,从 serde_json 的解析器直接读出来,没有出现 deserialize_any,也没有中间的 Content。数组形式 [3,4] 走 visit_seq,同样逐个直接读。变体标识用的是 deserialize_identifier,在 serde_json 里就是 deserialize_str,于是 visit_borrowed_str("box") 直接借用输入里的字符串,不分配。
不需要 deserialize_any,也就不挑格式:格式只要能按 deserialize_enum 的提示读出「哪个变体」,再按 VariantAccess 的提示读内容。bincode 的 variant_seed 读一个 u32 下标交给变体标识(走 visit_u64),见 SeqAccess、MapAccess 与 EnumAccess。src/bin/bincode_reprs.rs 把索引页那四个只差属性的 enum 各用 bincode 1.3.3 往返一次(外层 Ok 是序列化,内层是反序列化):
External Dot -> Ok(Ok(Dot))
External At(Point { x: 1, y: 2 }) -> Ok(Ok(At(Point { x: 1, y: 2 })))
External Rect { w: 3, h: 4 } -> Ok(Ok(Rect { w: 3, h: 4 }))
Internal Dot -> Ok(Err(DeserializeAnyNotSupported))
Internal At(Point { x: 1, y: 2 }) -> Ok(Err(DeserializeAnyNotSupported))
Internal Rect { w: 3, h: 4 } -> Ok(Err(DeserializeAnyNotSupported))
Adjacent Dot -> Ok(Err(Custom("Bincode does not support Deserializer::deserialize_identifier")))
Adjacent At(Point { x: 1, y: 2 }) -> Ok(Err(Custom("Bincode does not support Deserializer::deserialize_identifier")))
Adjacent Rect { w: 3, h: 4 } -> Ok(Err(Custom("Bincode does not support Deserializer::deserialize_identifier")))
Untagged Dot -> Ok(Err(DeserializeAnyNotSupported))
Untagged At(Point { x: 1, y: 2 }) -> Ok(Err(DeserializeAnyNotSupported))
Untagged Rect { w: 3, h: 4 } -> Ok(Err(DeserializeAnyNotSupported))
只有外部标签三种变体都回来了。内部标签和 untagged 卡在 deserialize_any(DeserializeAnyNotSupported);相邻标签的入口是 deserialize_struct,bincode 把它当序列交给生成的 visit_seq,而 visit_seq 读第一个元素(标签)用的是 next_element::<__Field>(),__Field::deserialize 调 deserialize_identifier,bincode 1.3.3 不支持。
#[serde(other)]:兜底的 unit 变体
#[serde(other)] 标在一个 unit 变体上,让不认识的变体名都落到它。prepare_enum_variant_enum 把它变成变体标识的兜底分支:
let fallthrough = deserialized_variants
.clone()
.find(|(_i, variant)| variant.attrs.other())
.map(|(i, _variant)| {
let ignore_variant = field_i(i);
quote!(_serde::#private::Ok(__Field::#ignore_variant))
});
有了 fallthrough,visit_str / visit_bytes 的 _ => 分支就不再是 unknown_variant,而是 Ok(__Field::__field2)。check_identifier 在编译期限制它:只能有一个、必须在最后一个变体上、必须是 unit 变体、不能用在 untagged enum 上(报 #[serde(other)] cannot appear on untagged enum)。这个属性是 1.0.79 加的(PR #1382)。
src/bin/ext_other.rs other:
#[derive(Serialize, Deserialize, Debug)]
enum Event {
Click { x: i32 },
Key(char),
#[serde(other)]
Unknown,
}
json::<Event>(r#"{"Click":{"x":1}}"#);
json::<Event>(r#""Scroll""#);
json::<Event>(r#"{"Scroll":null}"#);
{"Click":{"x":1}} => Ok(Click { x: 1 })
"Scroll" => Ok(Unknown)
{"Scroll":null} => Ok(Unknown)
字符串形式的 "Scroll" 和带 null 的 {"Scroll":null} 都落到 Unknown。注意兜底只换掉了「变体名认不出」这一步:变体标识成了 Unknown,visit_enum 接着照 Unknown 的形状调 unit_variant,内容那一步仍由格式检查。
还是上面的 Event,输入换成 {"Scroll":{"dy":3}}:Scroll 不认识,落到 Unknown。serde_json::from_str::<Event> 返回 Ok(Unknown) 还是错误?如果是错误,报在第几列?
#[serde(other)] 也接住未知的数字下标。bincode 写变体只写下标,src/bin/ext_other.rs index 构造一个下标为 9 的输入:
let key = bincode::serialize(&Event::Key('k')).unwrap();
println!("Event::Key as bincode: {key:?}");
let unknown = [9u8, 0, 0, 0];
let e: Result<Event, _> = bincode::deserialize(&unknown);
println!("{unknown:?} as Event: {e:?}");
Event::Key as bincode: [1, 0, 0, 0, 107]
[9, 0, 0, 0] as Event: Ok(Unknown)
Event 展开后的 visit_u64 里,越界分支也是兜底:
match __value {
0u64 => _serde::__private229::Ok(__Field::__field0),
1u64 => _serde::__private229::Ok(__Field::__field1),
2u64 => _serde::__private229::Ok(__Field::__field2),
_ => _serde::__private229::Ok(__Field::__field2),
}
源码里是 u64_fallthrough_arm:有 fallthrough 就用它,没有才生成 "variant index 0 <= i < N" 的 invalid_value。
1.0.122 之前,#[serde(other)] 的兜底只接在 visit_str / visit_bytes 上,visit_u64 的越界分支总是报 variant index 0 <= i < N。PR #1914(「Consistently handle numeric and string/bytes keys of field names」)的提交 07374746 把它改成上面的 u64_fallthrough_arm,提交说明是「Allow field identifiers be any numbers if #[serde(other)] is used. Thus behavior synchronized between string/bytes identifiers and numeric identifiers」。PR 的动机是字段标识(给内部标签用的 field_identifier enum),但改的是字段和变体共用的 deserialize_identifier,所以变体标识的 other 也一起受益:bincode 里出现未知的变体下标时落到 Unknown,而不是报错。首次发布是 1.0.122,发布说明写的是「Handle unrecognized numeric field keys during deserialization of a field_identifier, equivalently to string field keys (#1914)」。
下标还牵出一个现存的不对称。生成的 Serialize 用变体在源码里的位置做下标,而变体标识的 visit_u64 只给参与反序列化的变体编号(deserialized_variants 先过滤掉了 skip_deserializing 的)。中间有一个变体被跳过,后面的下标就错开了。src/bin/ext_other.rs gap:
#[derive(Serialize, Deserialize, Debug)]
enum Gap {
A,
#[serde(skip_deserializing)]
B,
C,
}
Gap::C as bincode: [2, 0, 0, 0]
bincode back: Err(Custom("invalid value: integer `2`, expected variant index 0 <= i < 2"))
Gap::C 序列化成下标 2(展开里是 serialize_unit_variant(__serializer, "Gap", 2u32, "C")),反序列化一侧的 visit_u64 却是 0u64 => __field0、1u64 => __field2,2 越界。JSON 按名字认变体,不受影响;只按下标认的格式会读错或读不回来。这是一个开着的问题:issue #2614(「Binary serialize/deserialize inconsistency of enums with skipped fields」),里面的例子用 #[serde(skip)],bincode 和 postcard 下 C 被读成了 D。
variant_identifier:只要变体标识
#[serde(variant_identifier)] 让一个只有 unit 变体的 enum 的 Deserialize 只生成「认变体名」那一半:不调 deserialize_enum,而是 deserialize_custom 生成的 deserialize_identifier(__deserializer, __FieldVisitor),值就是 enum 自己。它和 field_identifier 是一对,用途是手写 visit_enum 时当 variant_seed 的类型。serde 自己的 OsString 就是这么写的:serde_core 里不能用 derive,于是用一个宏 variant_identifier! 手工生成同样的东西,注释写着「If this were outside of the serde crate, it would just use: #[derive(Deserialize)] #[serde(variant_identifier)]」:
variant_identifier! {
OsStringKind (Unix; b"Unix"; 0, Windows; b"Windows"; 1)
"`Unix` or `Windows`",
OSSTR_VARIANTS
}
OsStringVisitor 的 visit_enum 里 match tri!(data.variant()) { (OsStringKind::Unix, v) => v.newtype_variant().map(OsString::from_vec), ... },读出变体后自己决定怎么读内容,这正是 derive 的 visit_enum 做的事。
单独拿来反序列化时,它就是一个标识符(src/bin/ext_other.rs ident):
#[derive(Deserialize, Debug)]
#[serde(variant_identifier)]
enum Kind {
Circle,
Square,
}
"Square" => Ok(Square)
1 => Err(invalid type: integer `1`, expected variant identifier at line 1 column 1)
{"Square":null} => Err(invalid type: map, expected variant identifier at line 1 column 0)
"Hexagon" => Err(unknown variant `Hexagon`, expected `Circle` or `Square` at line 1 column 9)
"Square" 能读,{"Square":null} 不能:这不是外部标签的 enum,没有 deserialize_enum 那一层。1 也不行:serde_json 的 deserialize_identifier 是 deserialize_str,数字直接 invalid type;下标那条路只有 bincode 这类格式的 variant_seed 会走。只有两个名字时,unknown_variant 把列表写成 expected `Circle` or `Square`。check.rs 里还规定 variant_identifier 的变体只能是 unit,也不能带 #[serde(other)](#[serde(other)] may not be used on a variant identifier)。
动脑筋答案
src/bin/ext_other.rs quiz:
{"Scroll":{"dy":3}} => Err(invalid type: map, expected unit at line 1 column 10)
是错误,报在第 10 列。Scroll 经兜底变成 __Field::__field2(Unknown),visit_enum 按 Unknown 是 unit 变体调 unit_variant。serde_json 的 VariantAccess::unit_variant 是 de::Deserialize::deserialize(self.de),也就是从冒号后面读一个 ():读到 null 才行,读到 { 就是 invalid type: map, expected unit。第 10 列正是 {"Scroll": 之后那个 {。#[serde(other)] 只能接住「名字不认识、内容为空」的变体;要连内容一起吞掉,可以把兜底变体写成 #[serde(untagged)] Other(serde_json::Value)(变体级的 untagged,1.0.164 起,PR #2403)。src/bin/untagged_other.rs 对同一个输入的结果:
Ok(Other(Object {"Scroll": Object {"dy": Number(3)}}))
代价是整个 enum 改走缓冲:生成代码先把整个值读进 Content,在它上面按外部标签试一遍,失败了再交给 untagged 变体,见 untagged。
小结
- 外部标签是数据模型对 enum 的原生表示。序列化调
serialize_*_variant,serde_json 把 unit 变体写成"Dot",其余写成{"变体名":内容}。 - 生成的
Deserialize调deserialize_enum;visit_enum是一个match:EnumAccess::variant取出__Field,每个变体按形状调unit_variant、newtype_variant、tuple_variant或struct_variant,tuple / struct 变体复用 tuple struct / struct 的生成器。 - 变体标识没有
__ignore,不认识就unknown_variant;VARIANTS是全部可接受的名字(含 alias,按字符串排序),rename之后原名不再被接受。 - serde_json 只接受字符串或恰好一个键的对象;多一个键报的是
expected value,from_value一侧报map with a single key。 - 内容直接从格式读,没有
deserialize_any、没有Content,所以 bincode 这类非自描述格式也能用;四种表示里只有它能在 bincode 1.3.3 上往返。 #[serde(other)]只兜住变体名(字符串和 1.0.122 起的数字下标),内容仍按 unit 检查;skip_deserializing的变体会让按下标认变体的格式错位(issue #2614)。