untagged
主线位置:缓冲主线最典型的一支。生成的代码先用
ContentVisitor把输入整段读成Content(第一遍,格式驱动),再拿同一份Content按变体声明顺序一个个重放反序列化主线(第二遍起,ContentRefDeserializer驱动),第一个返回Ok的变体赢;全部失败时用de::Error::custom报一句固定的话。序列化一侧没有缓冲:变体直接按内容序列化,不写任何标签。
flowchart TB gen["生成的 deserialize"] --> cv["ContentVisitor 读成 Content"] cv --> ref["ContentRefDeserializer"] ref --> v1["试第 1 个变体"] v1 -->|Ok| win[返回] v1 -->|Err 丢弃| v2["试第 2 个变体"] v2 -->|Ok| win v2 -->|Err 丢弃| more["……"] more --> fail["custom 一句话"]
Content 的定义、它没有的 u128 / i128、ContentRefDeserializer 怎么把 deserialize_u8 之类的提示映射到 Content 的几种整数,都在 Content:缓冲的代价。内部标签也缓冲,但只重放一次,见内部标签与相邻标签。本页实验都在 M2 Max、macOS、rustc 1.99.0 上运行,依赖 serde 1.0.229、serde_json 1.0.154。
生成的代码
enum_untagged.rs 的 deserialize 的模板:
quote_block! {
let __content = _serde::de::DeserializeSeed::deserialize(_serde::#private::de::ContentVisitor::new(), __deserializer)?;
let __deserializer = _serde::#private::de::ContentRefDeserializer::<__D::Error>::new(&__content);
#first_attempt
#(
if let _serde::#private2::Ok(__ok) = #attempts {
return _serde::#private2::Ok(__ok);
}
)*
_serde::#private::Err(_serde::de::Error::custom(#fallthrough_msg))
}
对一个两变体的 enum 展开:
#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum Shape {
Circle(Circle), // struct Circle { r: f64 }
Rect(Rect), // struct Rect { w: f64, h: f64 }
}
let __content = _serde::de::DeserializeSeed::deserialize(
_serde::__private229::de::ContentVisitor::new(),
__deserializer,
)?;
let __deserializer = _serde::__private229::de::ContentRefDeserializer::<
__D::Error,
>::new(&__content);
if let _serde::__private229::Ok(__ok) = _serde::__private229::Result::map(
<Circle as _serde::Deserialize>::deserialize(__deserializer),
Shape::Circle,
) {
return _serde::__private229::Ok(__ok);
}
if let _serde::__private229::Ok(__ok) = _serde::__private229::Result::map(
<Rect as _serde::Deserialize>::deserialize(__deserializer),
Shape::Rect,
) {
return _serde::__private229::Ok(__ok);
}
_serde::__private229::Err(
_serde::de::Error::custom(
"data did not match any variant of untagged enum Shape",
),
)
- 第一遍只有一个
?。ContentVisitor作为 seed 调__deserialize_content_v1,默认实现就是deserialize_any。这里的错误是格式自己的错误(语法错、EOF),原样带位置返回。 - 之后每个变体一个
if let Ok。Err分支不存在:失败的结果被if let直接丢掉,连错误值都没有绑定。 - 同一个
__deserializer用了多次。Deserializer的方法按值拿self,能这样写是因为ContentRefDeserializer只是&Content加一个PhantomData,实现了Copy。每次尝试拿到的都是一份新的拷贝,从树根开始读。也因为要读很多次,这里只能借用Content,不能用拿走所有权的ContentDeserializer。借用的代价是:Content::String这样拥有所有权的节点,ContentRefDeserializer::deserialize_str只能交出visit_str(v),读成String时要再复制一份。
#attempts 由 deserialize_variant 逐个变体生成,按形状分四种:
| 变体 | 生成的尝试 | 面对 ContentRefDeserializer 调的方法 |
|---|---|---|
| unit | deserialize_any(UntaggedUnitVisitor) | deserialize_any |
newtype V(T) | T::deserialize(__deserializer) | T 自己决定 |
tuple V(A, B) | TupleForm::Untagged 的代码 | deserialize_tuple(2, ..) |
struct V { .. } | StructForm::Untagged 的代码 | deserialize_any,只有 visit_map |
unit 变体用的 UntaggedUnitVisitor 只实现了 visit_unit 和 visit_none,所以 untagged 的 unit 变体对应的是 JSON 的 null,不是变体名。序列化一侧 serialize_untagged_variant 对 unit 变体也只调 serialize_unit:
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
enum Limit {
Unlimited,
Max(u32),
}
null
null => Ok(Unlimited)
5 => Ok(Max(5))
"Unlimited" => Err(Error("data did not match any variant of untagged enum Limit", line: 0, column: 0))
第一行是 to_string(&Limit::Unlimited),后三行是 from_str。
struct 变体那一行的「只有 visit_map」来自 struct_.rs 里的一句注释:「untagged struct variants do not get a visit_seq method.」普通 struct 能从 JSON 数组读出来(见生成的 Deserialize),untagged 的 struct 变体不行,newtype 变体里包一个 struct 却可以,因为那时用的是那个 struct 自己的 Deserialize:
struct Ok(Point { x: 1 })
variant Err(Error("data did not match any variant of untagged enum A", line: 0, column: 0))
newtype Ok(P(Point { x: 1 }))
三行的输入都是 [1],类型依次是 struct Point { x: i32 }、#[serde(untagged)] enum A { P { x: i32 } }、#[serde(untagged)] enum B { P(Point) }(src/bin/untagged_seq.rs)。
实验:每次尝试从哪里读
和内部标签那一页一样,用 Logged<T> 包住变体内容:它的 Deserialize 先打印拿到的 Deserializer 类型名,再用打印调用的 LogDe 包住往下读。src/bin/untagged_log.rs:
#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum Value {
Flag(Logged<bool>),
Circle(Logged<Circle>),
}
fn main() {
println!("{:?}", serde_json::from_str::<Value>(r#"{"r":2.5}"#));
}
[bool gets serde::private::de::content::ContentRefDeserializer]
deserialize_bool
[Circle gets serde::private::de::content::ContentRefDeserializer]
deserialize_struct("Circle", ["r"])
visit_map
next_key_seed
deserialize_identifier
visit_borrowed_str("r")
next_value_seed
deserialize_f64
visit_f64(2.5)
next_key_seed
=> None
Ok(Circle(Logged(Circle { r: 2.5 })))
- 两次尝试拿到的都是
ContentRefDeserializer,不是 serde_json。到这里 serde_json 已经把{"r":2.5}读完了。 bool的尝试调deserialize_bool,下面没有任何visit_*:Content::Map不是布尔值,ContentRefDeserializer直接报invalid type,这个错误被if let扔掉。Circle的尝试从树根重新开始,"r"仍是借用的字符串。
第一个成功的赢
尝试的顺序就是变体的声明顺序,一旦某个变体返回 Ok 就 return,后面的变体看都不看。于是「能读」不等于「读对」。src/bin/untagged_order.rs:
#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum Num {
Float(f64),
Int(i64),
}
#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum Shape {
Circle { r: f64 },
Labeled { r: f64, label: String },
}
#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum Shape2 {
Labeled { r: f64, label: String },
Circle { r: f64 },
}
#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum Id {
Text(String),
Num(u64),
}
fn main() {
println!("{:?}", serde_json::from_str::<Num>("1"));
println!("{:?}", serde_json::from_str::<Shape>(r#"{"r":1.0,"label":"a"}"#));
println!("{:?}", serde_json::from_str::<Shape2>(r#"{"r":1.0,"label":"a"}"#));
println!("{:?}", serde_json::from_str::<Id>("42"));
println!("{:?}", serde_json::from_str::<Id>(r#""42""#));
}
Ok(Float(1.0))
Ok(Circle { r: 1.0 })
Ok(Labeled { r: 1.0, label: "a" })
Ok(Num(42))
Ok(Text("42"))
- 整数被
f64抢走。1在Content里是U64(1),f64的Visitor实现了visit_u64(转成浮点数),所以Float在前就是Float(1.0),Int永远轮不到整数。 - 少字段的 struct 变体抢走多字段的输入。
Circle { r }没有deny_unknown_fields,"label"被当作未知字段忽略,Circle成功。把Labeled放到前面(Shape2)才得到想要的结果。另一个办法是在 enum 上加#[serde(untagged, deny_unknown_fields)],它作用于每个 struct 变体,同样的输入会得到Labeled(src/bin/untagged_deny.rs里跑过)。经验是:把要求最多、最具体的变体放在前面。 - 字符串和数字不会互相转换。
42不是字符串,String的尝试失败,落到Num;"42"是字符串,Text成功。缓冲本身不会改变「是什么类型」,Content保留了 serde_json 在第一遍报出的类型。
平台 M2 Max、macOS、rustc 1.99.0。把上面的 Num 反过来,整数在前(src/bin/untagged_quiz.rs):
#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum Num {
Int(i64),
Float(f64),
}
fn main() {
for s in ["1", "-1", "1.0", "18446744073709551615"] {
println!("{s:<22} => {:?}", serde_json::from_str::<Num>(s));
}
}
最后一个输入是 u64::MAX。四行各打印什么?
报错只剩一句
全部变体都失败时的消息在 deserialize 里拼好,作为字符串字面量写进生成的代码:
// TODO this message could be better by saving the errors from the failed
// attempts. The heuristic used by TOML was to count the number of fields
// processed before an error, and use the error that happened after the
// largest number of fields. I'm not sure I like that. Maybe it would be
// better to save all the errors and combine them into one message that
// explains why none of the variants matched.
let fallthrough_msg = format!(
"data did not match any variant of untagged enum {}",
params.type_name()
);
let fallthrough_msg = cattrs.expecting().unwrap_or(&fallthrough_msg);
这段 TODO 注释 说得很清楚:每次尝试的错误都没保存,所以没法说哪个变体为什么失败。可以换掉的只有这句话本身:容器属性 #[serde(expecting = "...")] 给了值就用它(见属性)。最后由 de::Error::custom 造出错误,用的是格式的错误类型,对 serde_json 就是 serde_json::Error。
src/bin/untagged_err.rs,Shape 同上,Shape2 多一个 expecting,Doc 是一个外层 struct:
#[derive(Deserialize, Debug)]
#[serde(untagged, expecting = "a circle {r} or a rect {w, h}")]
enum Shape2 {
Circle(Circle),
Rect(Rect),
}
#[derive(Deserialize, Debug)]
struct Doc {
name: String,
shape: Shape,
}
fn main() {
let bad = r#"{"w":1.0,"h":"2"}"#;
println!("{:?}", serde_json::from_str::<Shape>(bad));
println!("{:?}", serde_json::from_str::<Shape2>(bad));
let doc = r#"{"name":"a",
"shape":{"w":1.0,"h":"2"}}"#;
println!("{:?}", serde_json::from_str::<Doc>(doc));
println!("{:?}", serde_json::from_str::<Shape>(r#"{"w":1.0,"h":"#));
}
Err(Error("data did not match any variant of untagged enum Shape", line: 0, column: 0))
Err(Error("a circle {r} or a rect {w, h}", line: 0, column: 0))
Err(Error("data did not match any variant of untagged enum Shape", line: 2, column: 26))
Err(Error("EOF while parsing a value", line: 1, column: 13))
- 顶层的 untagged 错误没有位置。
line: 0表示没有位置。custom造错误时不知道位置,serde_json 的Deserializer在Visitor返回后才用fix_position补(见错误)。这个错误是在 serde_json 的deserialize_any返回之后才造出来的,没有人再补。 - 嵌在别的类型里就有位置,但不是出错的位置。
Doc一行的位置是line: 2, column: 26,是整个外层对象读完以后的位置。错误从Doc的visit_map冒出来,经过 serde_json 外层的deserialize_struct,它先end_map(),再对错误调fix_position。真正有问题的"2"在第 2 行第 22 列,这个信息已经没了。 - 语法错误不受影响。 最后一行是第一遍里 serde_json 自己报的 EOF,位置正常。
怎么查是哪个变体失败
serde 没有提供办法,只能在外面重做一遍「逐个尝试」,这次把错误留下来。把输入先读成 serde_json::Value(它和 Content 一样,是 deserialize_any 读出来的一棵树),再对每个变体的内容类型单独 from_value。src/bin/untagged_debug.rs:
fn try_as<T: DeserializeOwned>(name: &str, v: &Value) {
match serde_json::from_value::<T>(v.clone()) {
Ok(_) => println!(" {name}: ok"),
Err(e) => println!(" {name}: {e}"),
}
}
fn main() {
let s = r#"{"w":1.0,"h":"2"}"#;
if let Err(e) = serde_json::from_str::<Shape>(s) {
println!("Shape: {e}");
let v: Value = serde_json::from_str(s).unwrap();
try_as::<Circle>("Circle", &v);
try_as::<Rect>("Rect", &v);
}
}
Shape: data did not match any variant of untagged enum Shape
Circle: missing field `r`
Rect: invalid type: string "2", expected f64
Rect 那一行就是答案:h 写成了字符串。这个办法要求每个变体都是 newtype(内容是一个有名字的类型),struct 变体得先改写成 newtype 加一个独立的 struct 才能单独试。它重放的是 serde_json::Value 的 Deserializer,不是 ContentRefDeserializer,两者是不同的代码,调试时要记住这一层差别。
单个变体上的 #[serde(untagged)]
#[serde(untagged)] 也可以写在变体上:前面的变体照常带标签,后面标了 untagged 的变体作为兜底。enum_.rs 的 deserialize 在这里分岔:
// The variants have already been checked (in ast.rs) that all untagged variants appear at the end
match variants.iter().position(|var| var.attrs.untagged()) {
Some(variant_idx) => {
let (tagged, untagged) = variants.split_at(variant_idx);
let tagged_frag = Expr(deserialize_homogeneous_enum(params, tagged, cattrs));
// Ignore any error associated with non-untagged deserialization so that we
// can fall through to the untagged variants. This may be infallible so we
// need to provide the error type.
let first_attempt = quote! {
if let _serde::#private::Result::<_, __D::Error>::Ok(__ok) = (|| #tagged_frag)() {
return _serde::#private::Ok(__ok);
}
};
enum_untagged::deserialize(params, untagged, cattrs, Some(first_attempt))
}
None => deserialize_homogeneous_enum(params, variants, cattrs),
}
带标签的那一段变体按容器的表示(默认外部标签)整体生成一份代码,包进一个闭包,作为 untagged 模板里的 #first_attempt。注意它插在 let __deserializer = ContentRefDeserializer::new(&__content); 之后:带标签的变体也是对 Content 重放,整段输入照样先缓冲。cargo expand 的结果(变体 Num(u32)、Op(char)、#[serde(untagged)] Raw(String)):
let __content = _serde::de::DeserializeSeed::deserialize(
_serde::__private229::de::ContentVisitor::new(),
__deserializer,
)?;
let __deserializer = _serde::__private229::de::ContentRefDeserializer::<
__D::Error,
>::new(&__content);
if let _serde::__private229::Result::<_, __D::Error>::Ok(__ok) = (|| {
// ...
#[doc(hidden)]
const VARIANTS: &'static [&'static str] = &["Num", "Op"];
_serde::Deserializer::deserialize_enum(
__deserializer,
"Token",
VARIANTS,
__Visitor {
marker: _serde::__private229::PhantomData::<Token>,
lifetime: _serde::__private229::PhantomData,
},
)
})() {
return _serde::__private229::Ok(__ok);
}
if let _serde::__private229::Ok(__ok) = _serde::__private229::Result::map(
<String as _serde::Deserialize>::deserialize(__deserializer),
Token::Raw,
) {
return _serde::__private229::Ok(__ok);
}
_serde::__private229::Err(
_serde::de::Error::custom(
"data did not match any variant of untagged enum Token",
),
)
src/bin/untagged_variant.rs:
#[derive(Serialize, Deserialize, Debug)]
enum Token {
Num(u32),
Op(char),
#[serde(untagged)]
Raw(String),
}
fn main() {
for s in [r#"{"Num":1}"#, r#"{"Op":"+"}"#, r#""abc""#, r#"{"Num":"x"}"#, "1"] {
println!("{s:<12} => {:?}", serde_json::from_str::<Token>(s));
}
for t in [Token::Num(1), Token::Raw("abc".into())] {
println!("{t:?} => {}", serde_json::to_string(&t).unwrap());
}
}
{"Num":1} => Ok(Num(1))
{"Op":"+"} => Ok(Op('+'))
"abc" => Ok(Raw("abc"))
{"Num":"x"} => Err(Error("data did not match any variant of untagged enum Token", line: 0, column: 0))
1 => Err(Error("data did not match any variant of untagged enum Token", line: 0, column: 0))
Num(1) => {"Num":1}
Raw("abc") => "abc"
{"Num":"x"}的标签认出来了,失败在内容上,这个invalid type也被丢掉,报的仍是那句话,而且说Token是「untagged enum」,虽然它的大部分变体带标签。- 序列化一侧没有回退的问题:
Num(1)照外部标签写,Raw直接写内容。
untagged 变体必须放在最后。检查在 ast.rs 的 index_of_last_tagged_variant:找到最后一个不带 untagged 的变体,它前面出现的 untagged 变体都报错。
#[derive(Deserialize, Debug)]
enum Token {
#[serde(untagged)]
Raw(String),
Num(u32),
}
error: all variants with the #[serde(untagged)] attribute must be placed at the end of the enum
--> src/bin/untagged_variant_order.rs:6:3
|
6 | Raw(String),
| ^^^
这个功能来自 PR #2403「Allowed Enum variants to be individually marked as untagged」,首次发布在 1.0.164(发布说明:「Allowed enum variants to be individually marked as untagged (#2403)」)。在这之前,要达到同样的效果得手写三个类型:一个只含带标签变体的 enum、一个把它当作第一个变体的 untagged enum、再用 #[serde(from = ..., into = ...)] 把原 enum 和后者互转。PR 说明写道:「This can be thought of as applying the following transformation」,并且「The actual implementation generates the implementation directly allowing it to also work efficiently for recursive enums.」变体必须放在最后的限制也写在里面:「in order to keep the implementation strategy consistent with an alternate strategy of attempting to deserialize each variant in order.」同一个版本里还有一个提交「Reuse a single ContentRefDeserializer throughout untagged enum deserialization」(f60324e8),就是前面那个被反复使用的 __deserializer。
动脑筋答案
1 => Ok(Int(1))
-1 => Ok(Int(-1))
1.0 => Ok(Float(1.0))
18446744073709551615 => Ok(Float(1.8446744073709552e19))
1和-1:Content里是U64(1)和I64(-1),i64的Visitor两种都接受,Int先试先赢。1.0:Content里是F64,i64不接受浮点数,落到Float。18446744073709551615:Content里是U64(u64::MAX)。i64的visit_u64检查范围,超出就报错;f64的visit_u64直接转换,得到1.8446744073709552e19,精度丢了也不报错。调换顺序修好了上一节的Float(1.0),却又让超出i64的整数悄悄变成了浮点数。
小结
- 生成的代码:
ContentVisitor读成Content(唯一一个?),ContentRefDeserializer借用它,每个变体一个if let Ok(..) = .. { return },最后de::Error::custom(...)。 ContentRefDeserializer是Copy的,每次尝试都从树根重读;失败的错误连绑定都没有。- unit 变体只认
null;struct 变体没有visit_seq,读不了数组。 - 顺序就是声明顺序,第一个
Ok赢:f64吃掉整数、少字段的 struct 吃掉多字段的输入,具体的变体要放前面。 - 报错是
data did not match any variant of untagged enum X,可用expecting换掉;顶层没有位置,嵌套时的位置是外层读完的地方。 - 调试:先读成
serde_json::Value,对每个变体的内容类型单独from_value,看各自的错误。 - 单个变体上的
#[serde(untagged)](1.0.164,#2403):带标签的变体先作为整体对Content试一次,失败再试 untagged 变体;untagged 变体必须放在最后。