内部标签与相邻标签
主线位置:缓冲主线的一支。内部标签
#[serde(tag = "type")]的反序列化先用TaggedContentVisitor把整个对象读一遍,拿出标签,其余键值缓冲成Content,再用ContentDeserializer对选中的变体重放一次反序列化主线。相邻标签#[serde(tag = "t", content = "c")]只在内容出现在标签之前时才缓冲。序列化一侧两者都不缓冲:内部标签靠TaggedSerializer把标签插进内容的第一个字段,相邻标签就是一个两字段的 struct。
flowchart TB
gen["生成的 deserialize"] --> tcv["TaggedContentVisitor"]
tcv --> loopk{下一个键}
loopk -->|"是 type"| tagv["读标签值 __Field"]
loopk -->|其他键| buf["键值读成 Content"]
tagv --> loopk
buf --> loopk
loopk -->|没有了| pair["返回 标签 和 Content"]
pair --> cd["ContentDeserializer"]
cd --> arm["按标签选一个变体重放"]
外部标签(默认表示)不缓冲,读法见外部标签;Content 这棵树本身、它丢掉了哪些信息、ContentDeserializer 怎么把 deserialize_u32 映射到 Content::U64,见 Content:缓冲的代价。这一页只讲两种带标签的表示怎么用上它。本页实验都在 M2 Max、macOS、rustc 1.99.0 上运行,依赖 serde 1.0.229、serde_json 1.0.154。
两种表示长什么样
同一个变体 Data { id: 1 }:
| 表示 | 属性 | JSON |
|---|---|---|
| 外部标签 | 无 | {"Data":{"id":1}} |
| 内部标签 | #[serde(tag = "type")] | {"type":"Data","id":1} |
| 相邻标签 | #[serde(tag = "t", content = "c")] | {"t":"Data","c":{"id":1}} |
内部标签把标签和内容的字段放在同一个对象里,读的时候不知道标签在第几个键。相邻标签把两者分开放在两个键里,读到哪一个先都能分辨。这一点差别决定了两者缓冲的方式。
内部标签:生成的代码
enum_internally.rs 的 deserialize 生成的函数体只有三步。下面是 cargo expand 的输出,enum 有 Ping(unit)、Point(Point)(newtype)、Data { id: u32 }(struct)等变体:
let (__tag, __content) = _serde::Deserializer::deserialize_any(
__deserializer,
_serde::__private229::de::TaggedContentVisitor::<
__Field,
>::new("type", "internally tagged enum Msg"),
)?;
let __deserializer = _serde::__private229::de::ContentDeserializer::<
__D::Error,
>::new(__content);
match __tag {
__Field::__field0 => {
_serde::Deserializer::deserialize_any(
__deserializer,
_serde::__private229::de::InternallyTaggedUnitVisitor::new(
"Msg",
"Ping",
),
)?;
_serde::__private229::Ok(Msg::Ping)
}
__Field::__field1 => {
_serde::__private229::Result::map(
<Point as _serde::Deserialize>::deserialize(__deserializer),
Msg::Point,
)
}
// ...
}
- 对格式调
deserialize_any,Visitor是TaggedContentVisitor<__Field>,得到(__tag, __content)。这里直接调deserialize_any,不经过ContentVisitor用的__deserialize_content_v1;对象里每个值才经ContentVisitor读成Content。 - 把
__content交给ContentDeserializer,并且遮蔽了原来的__deserializer:后面的代码以为自己面对的还是一个格式。 - 按标签
match。newtype 变体就是Point::deserialize(__deserializer),struct 变体是一段完整的 struct 反序列化代码(StructForm::InternallyTagged,用deserialize_any),unit 变体见后面的InternallyTaggedUnitVisitor。
这里用的是拿走所有权的 ContentDeserializer,不是 untagged 用的 ContentRefDeserializer:标签已经定了只试一个变体,Content 用一次就可以扔掉,String 之类可以直接移交,不用再克隆。untagged 要拿同一份 Content 试好几次,只能借用,见 untagged。
TaggedContentVisitor:标签之外全部缓冲
TaggedContentVisitor 的 Value 是 (T, Content<'de>)。它的 visit_map:
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut tag = None;
let mut vec = Vec::<(Content, Content)>::with_capacity(size_hint::cautious::<(
Content,
Content,
)>(map.size_hint()));
while let Some(k) = tri!(map.next_key_seed(TagOrContentVisitor::new(self.tag_name))) {
match k {
TagOrContent::Tag => {
if tag.is_some() {
return Err(de::Error::duplicate_field(self.tag_name));
}
tag = Some(tri!(map.next_value()));
}
TagOrContent::Content(k) => {
let v = tri!(map.next_value_seed(ContentVisitor::new()));
vec.push((k, v));
}
}
}
match tag {
None => Err(de::Error::missing_field(self.tag_name)),
Some(tag) => Ok((tag, Content::Map(vec))),
}
}
- 每个键都先过一遍
TagOrContentVisitor。 它作为 seed 调deserialize_any(注释是「Internally tagged enums are only supported in self-describing formats.」),在visit_str/visit_borrowed_str/visit_string/visit_bytes等方法里把键和标签名比较:相等返回TagOrContent::Tag,否则把键本身交给ContentVisitor变成Content。键不一定是字符串,visit_bool、visit_u64这些也都实现了,直接存成Content。 - 标签的值不进
Content。map.next_value()的类型是T,也就是生成的__Field,直接从格式读,走deserialize_identifier。 - 其余的值进
Content。next_value_seed(ContentVisitor::new()),整棵子树都读进来。 - 循环不会在找到标签后停下。 标签前面的键值、后面的键值都推进同一个
vec,最后包成Content::Map(vec)。这个 map 里没有标签那一项。 - 标签出现两次报
duplicate_field,没有标签报missing_field。
用 一次往返 里那个打印调用的 LogDe 包住 serde_json 的 Deserializer,看第一遍读了什么;再给变体里的类型套一层 Logged<T>,它的 Deserialize 先打印自己拿到的 Deserializer 类型名,再用 LogDe 包住它往下读,这样能看到第二遍(重放)。src/bin/internal_log.rs:
#[derive(Deserialize, Debug)]
struct Inner {
id: u32,
name: String,
}
#[derive(Deserialize, Debug)]
#[serde(tag = "type")]
enum Msg {
Ping,
Data(Logged<Inner>),
}
fn run(json: &str) {
println!("== {json}");
let mut de = serde_json::Deserializer::from_str(json);
let r = Msg::deserialize(LogDe(&mut de));
println!("=> {r:?}");
}
fn main() {
run(r#"{"id":1,"type":"Data","name":"x"}"#);
}
标签故意放在中间:
== {"id":1,"type":"Data","name":"x"}
deserialize_any
visit_map
next_key_seed
deserialize_any
visit_borrowed_str("id")
next_value_seed
deserialize_any
visit_u64(1)
next_key_seed
deserialize_any
visit_borrowed_str("type")
next_value_seed
deserialize_identifier
visit_borrowed_str("Data")
next_key_seed
deserialize_any
visit_borrowed_str("name")
next_value_seed
deserialize_any
visit_borrowed_str("x")
next_key_seed
=> None
[Inner gets serde::private::de::content::ContentDeserializer]
deserialize_struct("Inner", ["id", "name"])
visit_map
next_key_seed
deserialize_identifier
visit_borrowed_str("id")
next_value_seed
deserialize_u32
visit_u64(1)
next_key_seed
deserialize_identifier
visit_borrowed_str("name")
next_value_seed
deserialize_string
visit_borrowed_str("x")
next_key_seed
=> None
=> Ok(Data(Logged(Inner { id: 1, name: "x" })))
- 第一遍只有一个
deserialize_any。三个键都用deserialize_any读;"id"和"name"的值也是deserialize_any(ContentVisitor走__deserialize_content_v1,LogDe没覆盖它,默认实现转给deserialize_any,serde_json 也没覆盖)。只有标签的值用了deserialize_identifier。 "id"在标签之前,照样被读成Content;"name"在标签之后,也一样。- 第二遍里
Inner拿到的是ContentDeserializer,它看到的 map 只有id和name两个键。Inner给出了deserialize_u32的提示,Content里存的是U64,回调的还是visit_u64。字符串在第一遍是借用的&'de str,重放时仍是visit_borrowed_str。
问: 标签要是第一个键,读到它就知道是哪个变体了,为什么还要把后面的键值缓冲起来?
答: 因为 TaggedContentVisitor 是一个和具体变体无关的泛型 Visitor:它只知道标签的类型 T(生成的 __Field),手里没有任何变体的 Visitor,读到标签以后也没法把剩下的 MapAccess 交给某个变体去读,只能把剩下的东西装成一个值还给生成的代码,这个值就是 Content。下面的相邻标签不同,它的 visit_map 是 derive 为这个 enum 现生成的,生成时就知道所有变体,所以读到标签以后能直接用 __Seed 从格式读内容。后面的分配计数表也说明了这一点:内部标签不管标签在前在后,分配次数一样。
输入是数组的时候
visit_seq 把第一个元素当标签,剩下的 SeqAccess 用公开的 de::value::SeqAccessDeserializer 包起来,交给 ContentVisitor 读成 Content(见 value 模块):
fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
where
S: SeqAccess<'de>,
{
let tag = match tri!(seq.next_element()) {
Some(tag) => tag,
None => {
return Err(de::Error::missing_field(self.tag_name));
}
};
let rest = de::value::SeqAccessDeserializer::new(seq);
Ok((tag, tri!(ContentVisitor::new().deserialize(rest))))
}
所以 ["Data", 7] 也能读成 Data { id: 7 }:struct 变体生成的 __Visitor 有 visit_seq,按位置取字段。
visit_seq 是 PR #1058「Support deserializing internally tagged enum from seq」加的,首次发布在 1.0.15。说明里写:「During serialization, internally tagged enums invoke the Serializer's serialize_struct. In JSON this turns into a map which uses visit_map when deserialized. But some formats employ visit_seq when deserializing a struct. One example is rmp-serde. Such formats were previously unable to deserialize an internally tagged enum.」在这之前 TaggedContentVisitor 只有 visit_map。1.0.15 的发布说明是「Support for deserializing internally tagged enums in MessagePack (#1058)」。
实验:几种输入
src/bin/internal_de.rs,enum 是:
#[derive(Deserialize, Debug)]
#[serde(tag = "type")]
enum Msg {
Ping,
Point(Point), // struct Point { x: i32, y: i32 }
Shape(Shape), // 外部标签 enum Shape { Circle(f64) }
Unit(()),
Data { id: u32 },
}
每个输入调一次 serde_json::from_str::<Msg>:
{"type":"Ping"} => Ok(Ping)
{"type":"Ping","extra":[1,2]} => Ok(Ping)
{"x":1,"y":2,"type":"Point"} => Ok(Point(Point { x: 1, y: 2 }))
{"type":"Shape","Circle":1.5} => Ok(Shape(Circle(1.5)))
{"type":"Unit"} => Ok(Unit(()))
["Data", 7] => Ok(Data { id: 7 })
["Ping"] => Ok(Ping)
["Ping", 1] => Err(Error("invalid length 1, expected 0 elements in sequence", line: 0, column: 0))
{"id":7} => Err(Error("missing field `type`", line: 1, column: 8))
{"type":"Data","type":"Data","id":7} => Err(Error("duplicate field `type`", line: 1, column: 21))
{"type":"Nope"} => Err(Error("unknown variant `Nope`, expected one of `Ping`, `Point`, `Shape`, `Unit`, `Data`", line: 1, column: 14))
"Ping" => Err(Error("invalid type: string \"Ping\", expected internally tagged enum Msg", line: 1, column: 6))
- unit 变体容忍多余的键。
{"type":"Ping","extra":[1,2]}成功了。unit 变体的重放用InternallyTaggedUnitVisitor,它的visit_map用next_entry::<IgnoredAny, IgnoredAny>()把剩下的键值全部跳过,visit_seq直接返回Ok(())。 - 数组形式的 unit 变体不容忍多余元素。
["Ping", 1]失败了:visit_seq没有取元素就返回,ContentDeserializer重放完一个Content::Seq之后检查有没有剩下没读的元素,剩一个,报invalid length。 - 重放阶段的错误没有位置。
["Ping", 1]的错误是line: 0, column: 0,而missing field、duplicate field、unknown variant都带位置:前三个是在第一遍、serde_json 的deserialize_any里报的,serde_json 返回前用fix_position补上了位置;invalid length是ContentDeserializer在第二遍报的,那时 serde_json 早已返回,没人再补(line == 0表示没有位置,见错误)。 - newtype 变体里可以是 map 形状的任何东西。
Shape(Shape)包的是一个外部标签 enum,它在对象里占一个键"Circle";Unit(())包的是(),重放时面对的是一个空的Content::Map,也能读。 - 输入不是对象也不是数组时,
TaggedContentVisitor的expecting给出「internally tagged enum Msg」,这个字符串就是生成代码里new("type", "internally tagged enum Msg")的第二个参数,可以用容器属性#[serde(expecting = "...")]换掉(见属性)。
序列化一侧:TaggedSerializer
序列化不需要缓冲:标签在写第一个字段之前就知道。serialize_internally_tagged_variant 按变体形状分三种:
- unit 变体:
serialize_struct(type_name, 1),写一个字段tag: variant_name。 - struct 变体:照常
serialize_struct,长度加一,第一个字段是标签。 - newtype 变体:调
serialize_tagged_newtype,把内层的值交给一个包着真正格式的TaggedSerializer。
newtype 变体的难处在于:derive 生成代码时只看到字段的类型名,不知道它序列化出来是什么形状。TaggedSerializer 于是在运行期等内层的值自己说:它调哪个 serialize_*,就在哪里插标签。插得进去的只有 map 和 struct:
fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
let mut map = tri!(self.delegate.serialize_map(len.map(|len| len + 1)));
tri!(map.serialize_entry(self.tag, self.variant_name));
Ok(map)
}
fn serialize_struct(
self,
name: &'static str,
len: usize,
) -> Result<Self::SerializeStruct, Self::Error> {
let mut state = tri!(self.delegate.serialize_struct(name, len + 1));
tri!(state.serialize_field(self.tag, self.variant_name));
Ok(state)
}
serialize_map 和 serialize_struct 先开一个长度加一的 map / struct,写进标签,再把 SerializeMap / SerializeStruct 原样交还给内层,后面的字段由内层自己写。serialize_unit 和 serialize_unit_struct 写一个只有标签的 map;serialize_newtype_struct 剥掉一层继续往下;内层是 enum 时,unit、newtype、tuple、struct 四种变体都写成 map,标签之后再加一个以变体名为键的项(后两种在 alloc 下才有,因为要先缓冲内容)。
其余的方法都返回 bad_type 造的错误:
fn bad_type(self, what: Unsupported) -> S::Error {
ser::Error::custom(format_args!(
"cannot serialize tagged newtype variant {}::{} containing {}",
self.type_ident, self.variant_ident, what
))
}
Unsupported 是一个只为这句话存在的枚举,Display 给出「a boolean」「an integer」「a float」「a char」「a string」「a byte array」「an optional」「a sequence」「a tuple」「a tuple struct」。
src/bin/internal_ser.rs:
#[derive(Serialize)]
#[serde(tag = "type")]
enum Msg {
Ping,
Move { dx: i32 },
Point(Point),
Map(BTreeMap<String, u32>),
Unit(()),
Num(u32),
Text(String),
List(Vec<u32>),
}
逐个 serde_json::to_string:
ok: {"type":"Ping"}
ok: {"type":"Move","dx":3}
ok: {"type":"Point","x":1,"y":2}
ok: {"type":"Map","a":1}
ok: {"type":"Unit"}
err: cannot serialize tagged newtype variant Msg::Num containing an integer
err: cannot serialize tagged newtype variant Msg::Text containing a string
err: cannot serialize tagged newtype variant Msg::List containing a sequence
Num(u32)、Text(String)、List(Vec<u32>) 都编译通过了,错误到运行期才出现:一个整数、一个字符串、一个数组没有地方放 "type" 这个键。这条限制从内部标签诞生时就有,PR #739(0.9.6)的说明写的是「newtype variants must contain either a struct or a map and are serialized with "type": "T" inserted as the first field」。
tuple 变体则在编译期就被拒绝。检查在 attr.rs 的 decide_tag 里,只看字段个数(Unnamed 且不是 1 个字段):
#[derive(Serialize)]
#[serde(tag = "type")]
enum Msg {
Pair(u32, u32),
}
error: #[serde(tag = "...")] cannot be used with tuple variants
--> src/bin/tuple_variant_err.rs:6:3
|
6 | Pair(u32, u32),
| ^^^^^^^^^^^^^^
平台 M2 Max、macOS、rustc 1.99.0。src/bin/internal_quiz.rs:
#[derive(Serialize)]
enum Shape {
Circle(f64),
}
#[derive(Serialize)]
#[serde(tag = "type")]
enum Msg {
Maybe(Option<Point>), // struct Point { x: i32, y: i32 }
Shape(Shape),
}
fn main() {
let a = serde_json::to_string(&Msg::Maybe(Some(Point { x: 1, y: 2 })));
let b = serde_json::to_string(&Msg::Shape(Shape::Circle(1.5)));
println!("{a:?}");
println!("{b:?}");
}
Some(Point) 里面明明是一个 struct。两行各打印什么?
相邻标签:一个两字段的 struct
相邻标签的序列化最简单:serialize_adjacently_tagged_variant 生成 serialize_struct(type_name, 2),字段 t 是标签,字段 c 是内容(unit 变体只有 t,长度是 1)。内容是什么形状都行,Num(u32) 写成 {"t":"Num","c":7},没有内部标签的限制,tuple 变体也可以。
反序列化一侧,enum_adjacently.rs 的 deserialize 生成的入口是 deserialize_struct(type_name, &[tag, content], __Visitor),不是 deserialize_any。__Visitor 是为这个 enum 现生成的,它的 visit_map 看第一个有用的键是哪个:
match #next_relevant_key {
// First key is the tag.
_serde::#private::Some(_serde::#private::de::TagOrContentField::Tag) => {
// Parse the tag.
let __field = #variant_from_map;
// Visit the second key.
match #next_relevant_key {
// ...
// Second key is the content.
_serde::#private::Some(_serde::#private::de::TagOrContentField::Content) => {
let __ret = _serde::de::MapAccess::next_value_seed(&mut __map,
__Seed {
variant: __field,
marker: _serde::#private::PhantomData,
lifetime: _serde::#private::PhantomData,
})?;
// Visit remaining keys, looking for duplicates.
#visit_remaining_keys
}
// There is no second key; might be okay if the we have a unit variant.
_serde::#private::None => #missing_content
}
}
// First key is the content.
_serde::#private::Some(_serde::#private::de::TagOrContentField::Content) => {
// Buffer up the content.
let __content = _serde::de::MapAccess::next_value_seed(&mut __map, _serde::#private::de::ContentVisitor::new())?;
// Visit the second key.
match #next_relevant_key {
// Second key is the tag.
_serde::#private::Some(_serde::#private::de::TagOrContentField::Tag) => {
#finish_content_then_tag
}
// ...
- 标签在前。 先用
#variant_from_map读出标签,它是next_value_seed(AdjacentlyTaggedEnumVariantSeed::<__Field> { .. })。再遇到内容键时,直接next_value_seed(__Seed { variant: __field, .. }):__Seed是生成的DeserializeSeed,它按variant分派到各变体的读法(和 untagged 共用enum_untagged::deserialize_variant),面对的是格式本身,没有缓冲。 - 内容在前。 还不知道变体,只能
next_value_seed(ContentVisitor::new())把内容读成Content,等读到标签后,#finish_content_then_tag再用ContentDeserializer::new(__content)喂给__Seed。 - 其他键。 默认
#next_relevant_key用TagContentOtherFieldVisitor认键,不认识的键读成IgnoredAny跳过;加了deny_unknown_fields换成TagOrContentFieldVisitor,遇到第三种键直接报错。两者都走deserialize_identifier。 - 读完内容后
#visit_remaining_keys继续看后面还有没有重复的标签或内容键。缺内容键时,unit 变体直接成功;newtype 变体交给private::de::missing_field,内容类型是Option<u32>时得到None,是u32时报missing field `c`;tuple、struct 变体直接报missing field `c`。
实验:标签在前、在后
同样用 Logged<Inner> 看变体内容是从谁那里读的。src/bin/adjacent_log.rs:
#[derive(Deserialize, Debug)]
#[serde(tag = "t", content = "c")]
enum Msg {
Data(Logged<Inner>), // struct Inner { id: u32, name: String }
}
fn main() {
let first = r#"{"t":"Data","c":{"id":1,"name":"abcdef"}}"#;
let last = r#"{"c":{"id":1,"name":"abcdef"},"t":"Data"}"#;
for s in [first, last] {
println!("== {s}");
let r = serde_json::from_str::<Msg>(s);
println!("=> {r:?}");
}
// ...
}
== {"t":"Data","c":{"id":1,"name":"abcdef"}}
[Inner gets &mut serde_json::de::Deserializer]
deserialize_struct("Inner", ["id", "name"])
visit_map
next_key_seed
deserialize_identifier
visit_borrowed_str("id")
next_value_seed
deserialize_u32
visit_u64(1)
next_key_seed
deserialize_identifier
visit_borrowed_str("name")
next_value_seed
deserialize_string
visit_borrowed_str("abcdef")
next_key_seed
=> None
=> Ok(Data(Logged(Inner { id: 1, name: "abcdef" })))
== {"c":{"id":1,"name":"abcdef"},"t":"Data"}
[Inner gets serde::private::de::content::ContentDeserializer]
deserialize_struct("Inner", ["id", "name"])
visit_map
next_key_seed
deserialize_identifier
visit_borrowed_str("id")
next_value_seed
deserialize_u32
visit_u64(1)
next_key_seed
deserialize_identifier
visit_borrowed_str("name")
next_value_seed
deserialize_string
visit_borrowed_str("abcdef")
next_key_seed
=> None
=> Ok(Data(Logged(Inner { id: 1, name: "abcdef" })))
两次的调用序列一模一样,区别只在第一行:标签在前时 Inner 拿到的是 &mut serde_json::de::Deserializer,在后时拿到的是 ContentDeserializer。
调用序列看不出代价,数分配次数能看出来。一个计数的 #[global_allocator] 在 from_str 前后各读一次计数器,同一个 Inner { id: 1, name: "abcdef" } 放进五种表示(src/bin/alloc_count.rs,内部标签和相邻标签用 t 作标签名):
struct 1 {"id":1,"name":"abcdef"}
external 1 {"Data":{"id":1,"name":"abcdef"}}
internal first 2 {"t":"Data","id":1,"name":"abcdef"}
internal last 2 {"id":1,"name":"abcdef","t":"Data"}
adjacent first 1 {"t":"Data","c":{"id":1,"name":"abcdef"}}
adjacent last 2 {"c":{"id":1,"name":"abcdef"},"t":"Data"}
untagged 2 {"id":1,"name":"abcdef"}
- 那 1 次是
name: String自己的分配,谁都省不掉。 - 多出来的 1 次是
Content::Map里的Vec<(Content, Content)>。"abcdef"没有转义,在Content里是借用的Str,不另外分配。 - 相邻标签在前和外部标签一样,只有 1 次:没有缓冲。
- 内部标签在前在后都是 2 次:不论标签在哪,都缓冲。
1.0.181 之前,相邻标签的标签用 serialize_field("t", "Variant") 写成字符串,读的时候却用 __Field 的 deserialize_identifier。issue #2496「Revisit the representation of variant id for adjacently tagged enums」指出这在按序号编码变体的格式里是错的:「In formats that serialize externally tagged enums using the variant number instead of name, deserialize_identifier involves reading a number. It doesn't make sense to serialize something as a str and deserialize as number.」PR #2505 改成了现在的样子:序列化写 AdjacentlyTaggedEnumVariant,它的 Serialize 是 serialize_unit_variant(enum_name, variant_index, variant_name);反序列化在 visit_map 里用 AdjacentlyTaggedEnumVariantSeed 调 deserialize_enum,再 unit_variant()。JSON 输出不变(PR 说明:「I've run some tests with serde_json and the output is the same」)。首次发布在 1.0.181,发布说明是「Improve the representation of adjacently tagged enums in formats where enum tags are serialized by index, as opposed to by string name (#2505, #2496)」。
相邻标签与 bincode
既然入口是 deserialize_struct,标签又按 unit 变体写,相邻标签看起来应该能用在 bincode 这种非自描述格式上。试一下(src/bin/binary_formats.rs,Inner { id: u32 },变体 A、B):
let bytes = bincode::serialize(&Adjacent::B(Inner { id: 7 })).unwrap();
println!("adjacent bincode bytes: {bytes:?}");
println!("adjacent bincode back: {:?}", bincode::deserialize::<Adjacent>(&bytes));
// 内部标签、postcard 同理
adjacent bincode bytes: [1, 0, 0, 0, 7, 0, 0, 0]
adjacent bincode back: Err(Custom("Bincode does not support Deserializer::deserialize_identifier"))
adjacent json: {"t":"B","c":{"id":7}}
internal bincode bytes: [1, 0, 0, 0, 0, 0, 0, 0, 66, 7, 0, 0, 0]
internal bincode back: Err(DeserializeAnyNotSupported)
adjacent postcard bytes: [1, 7]
adjacent postcard back: Err(WontImplement)
internal postcard bytes: [1, 66, 7]
internal postcard back: Err(WontImplement)
- 序列化都成功了。相邻标签的字节是
[1, 0, 0, 0](变体序号 1,u32)加[7, 0, 0, 0]:标签确实按序号写了。内部标签写的是字符串"B"(长度 1 的 u64 加字节66)。 - 内部标签读不回来是预料之中的:
deserialize_any。 - 相邻标签读不回来,原因是另一条路。bincode 的
deserialize_struct把 struct 当元组读,回调的是visit_seq而不是visit_map。生成的visit_seq读第一个元素用的是SeqAccess::next_element(&mut __seq),类型是__Field,__Field的Deserialize调deserialize_identifier;bincode 1.3.3 和 postcard 1.1.3 都不实现这个方法。#2505 改的是visit_map里读标签的那一步,visit_seq没有改用AdjacentlyTaggedEnumVariantSeed。
所以在 serde 1.0.229 上,内部标签和相邻标签都只能和 bincode、postcard 单向配合:能写,读不回来。
动脑筋答案
Err(Error("cannot serialize tagged newtype variant Msg::Maybe containing an optional", line: 0, column: 0))
Ok("{\"type\":\"Shape\",\"Circle\":1.5}")
- 第一行:
Option<Point>的Serialize先调serialize_some,TaggedSerializer::serialize_some不看里面是什么,直接bad_type(Unsupported::Optional)。Some里面的Point根本没有机会调serialize_struct。 - 第二行:内层是外部标签 enum,
Shape::Circle(1.5)调serialize_newtype_variant,TaggedSerializer开一个两项的 map,先写标签,再写"Circle": 1.5。前面的internal_de实验里,同样的 JSON 也能读回Shape(Circle(1.5))。
小结
- 内部标签的生成代码三步:
deserialize_any(TaggedContentVisitor)得到(标签, Content),用ContentDeserializer遮蔽原来的__deserializer,按标签match一个变体重放。 TaggedContentVisitor::visit_map每个键都用deserialize_any读,等于标签名的那个键的值直接读成__Field,其余键值不论在标签前后都读成Content;输入是数组时第一个元素是标签(1.0.15,#1058)。- 只试一个变体,所以用拿走所有权的
ContentDeserializer;重放阶段报的错没有行列号。 - unit 变体用
InternallyTaggedUnitVisitor,对象里多余的键会被跳过。 - 序列化不缓冲:
TaggedSerializer等内层值调serialize_struct/serialize_map时插标签;整数、字符串、序列、Option都在运行期报cannot serialize tagged newtype variant ... containing ...;tuple 变体在编译期就被拒绝。 - 相邻标签是
deserialize_struct加一个生成的visit_map:标签在前时直接读内容,内容在前时缓冲成Content。标签按 unit 变体写(1.0.181,#2505),但visit_seq读标签仍走deserialize_identifier,bincode、postcard 读不回来。