missing field x
主线位置:反序列化主线的收尾。
visit_map循环结束后还是None的字段,由生成的代码决定填缺省值还是报错;不填缺省值时,Option能变成None全靠一个只会回答deserialize_option的假Deserializer。机制见生成的 Deserialize 的「缺的字段」。
flowchart TB
miss["字段缺失"] --> dflt{"有 default 吗"}
dflt -->|有| fill["填缺省值"]
dflt -->|没有| dw{"deserialize_with"}
dw -->|是| e1["直接报错"]
dw -->|否| mfd["交给字段类型读"]
mfd --> hint{"它调哪个提示"}
hint -->|deserialize_option| isnone["None"]
hint -->|其他| e2["missing field"]
本页实验在 M2 Max、macOS、rustc 1.99.0 上运行,依赖 serde = "=1.0.229"(derive)、serde_json = "=1.0.154"、serde_with = "3"(3.24.0)。
现象
「Option 字段缺了就是 None」这条规则只在字段类型字面上就是 Option 的时候成立,稍微包一层就不成立了。src/bin/missing_cases.rs,每个结构体一个字段 x,输入都是 {}(最后一个除外):
fn de_opt<'de, D: Deserializer<'de>>(d: D) -> Result<Option<u32>, D::Error> {
Option::<u32>::deserialize(d)
}
#[derive(Deserialize, Debug)]
struct Plain {
x: Option<u32>,
}
#[derive(Deserialize, Debug)]
struct With {
#[serde(deserialize_with = "de_opt")]
x: Option<u32>,
}
#[derive(Deserialize, Debug)]
struct Maybe(Option<u32>);
#[derive(Deserialize, Debug)]
struct Newtype {
x: Maybe,
}
#[derive(Deserialize, Debug)]
struct Json {
x: serde_json::Value,
}
#[derive(Deserialize, Debug)]
struct List {
x: Vec<u32>,
}
#[serde_as]
#[derive(Deserialize, Debug)]
struct SerdeAs {
#[serde_as(as = "Option<DisplayFromStr>")]
x: Option<u32>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Camel {
user_name: String,
}
Plain {} => Ok(Plain { x: None })
With {} => Err(missing field `x` at line 1 column 2)
Newtype {} => Err(missing field `x` at line 1 column 2)
Json {} => Err(missing field `x` at line 1 column 2)
List {} => Err(missing field `x` at line 1 column 2)
SerdeAs {} => Ok(SerdeAs { x: None })
Camel {"user_name":"a"} => Err(missing field `userName` at line 1 column 17)
位置都指向对象结尾的 }:缺字段要等整个对象读完才知道。最后一行先排除掉:报的名字是 userName,是 rename_all 改过的名字,输入里写的是 user_name。看到 missing field 先拿报出来的名字去输入里搜一下,名字对不上就是改名属性的问题(见属性的「名字」一节),不是本页的问题。
出处
字段缺失时填什么,由 serde_derive 的 expr_is_missing 在编译期决定,顺序是:字段上的 default、容器上的 default、最后才是报错。报错又分两种:
- 字段带
deserialize_with:生成return Err(<__A::Error as Error>::missing_field("x")),不看类型,de_opt根本不会被调用。 - 其余字段:调运行时的
missing_field,把一个假的MissingFieldDeserializer交给字段类型自己的Deserialize:
fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, E>
where
V: Visitor<'de>,
{
Err(Error::missing_field(self.0))
}
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, E>
where
V: Visitor<'de>,
{
visitor.visit_none()
}
serde_core::forward_to_deserialize_any! {
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
bytes byte_buf unit unit_struct newtype_struct seq tuple
tuple_struct map struct enum identifier ignored_any
}
只有 deserialize_option 回答 visit_none,其余 29 个提示全部转给 deserialize_any,报 Error::missing_field 的默认文字 missing field `{}` 。于是结果只取决于字段类型的 Deserialize 第一个调的是哪个提示:
| 字段类型 | 调的提示 | 缺失时 |
|---|---|---|
Option<u32> | deserialize_option | None |
Maybe(Option<u32>)(derive 的 newtype) | deserialize_newtype_struct | 报错,里面的 Option 没机会读 |
serde_json::Value | deserialize_any | 报错,不是 Value::Null |
Vec<u32> | deserialize_seq | 报错,不是空 Vec |
deserialize_with 的字段 | 不经过这里 | 报错 |
SerdeAs 那一行能得到 None,是因为 serde_with 的 #[serde_as] 宏替你补了属性。serde_with_macros 3.24.0 的 emit_default_annotation:字段类型和 as 类型都是 Option 且没写过 default 时,加一条 #[serde(default)];文档说「This restores the ability of accepting missing fields」,可以用 #[serde_as(no_default)] 关掉。条件是两边都是 Option:#[serde_as(as = "NoneAsEmptyString")] x: Option<String> 不满足,缺了照样报 missing field `x` (src/bin/missing_fix.rs 里跑过,见下文)。
为什么 deserialize_with 不也走 MissingFieldDeserializer,见生成的 Deserialize 里那个「没有蠢问题」(issue #2878 到 1.0.229 仍开着)。
Option 是 None」在 0.9 之前由格式决定0.8 的 MapVisitor 有一个 missing_field 方法,格式可以覆盖。issue #702(「Reconsider MapVisitor::missing_field」)统计过:「Out of all the Serde deserializers on GitHub, there are exactly four distinct behaviors for missing_field」,有的一律报错,有的 Option 当 None、其他报错,有的当成 unit,还有一个格式另有一套;作者倾向「standardize on one of these (my preference is None/error) and remove the ability for deserializers to modify the behavior」。提交 42bc63be「Standardize behavior of missing fields」删掉了 MapVisitor::missing_field 和 missing_field_seed,新建了 de/private.rs 里的 missing_field 和 MissingFieldDeserializer,doc 注释和今天一字不差:「If the missing field is of type Option<T> then treat is as None, otherwise it is an error.」它所在的分支 #706 并入 #710(「Errors redesign」,说明里写「fixes #702」)后合并,首次发布于 0.9.0。从那以后行为由类型决定、和格式无关,上面那张表对任何格式都一样。
修法
在字段上加 #[serde(default)],缺失时直接填 Default::default(),不再经过 MissingFieldDeserializer。src/bin/missing_fix.rs 的第一段:
#[derive(Deserialize, Debug, Default)]
struct Maybe(Option<u32>);
#[derive(Deserialize, Debug)]
struct Fixed {
#[serde(deserialize_with = "de_opt", default)]
a: Option<u32>,
#[serde(default)]
b: Maybe,
#[serde(default)]
c: serde_json::Value,
#[serde(default)]
d: Vec<u32>,
}
#[serde_as]
#[derive(Deserialize, Debug)]
struct EmptyStr {
#[serde_as(as = "NoneAsEmptyString")]
x: Option<String>,
}
fn main() {
println!("{:?}", serde_json::from_str::<Fixed>("{}"));
println!("{:?}", serde_json::from_str::<EmptyStr>("{}"));
println!("{:?}", serde_json::from_str::<EmptyStr>(r#"{"x":""}"#));
// ...
}
Ok(Fixed { a: None, b: Maybe(None), c: Null, d: [] })
Err(Error("missing field `x`", line: 1, column: 2))
Ok(EmptyStr { x: None })
- 四个字段都拿到了各自类型的缺省值:
Value的缺省是Null,Vec是空的。Maybe要先derive(Default)。 NoneAsEmptyString要自己写default:它只把""变成None,不管缺失。
newtype 还有一个不加 default 的办法:#[serde(transparent)]。它不调 deserialize_newtype_struct,直接用里面那个字段的 Deserialize(deserialize_transparent),Option 又能看到 deserialize_option 了。代价是格式也看不到这一层 newtype,下面的动脑筋里有它。
容器上的 default:每次都调
在容器上写 #[serde(default)] 一次覆盖所有字段,但它生成的是一行无条件执行的 let __default: Self::Value = Default::default();,字段全都给了也调。missing_fix.rs 的第二段:
#[derive(Deserialize, Debug)]
#[serde(default)]
struct Config {
port: u16,
host: String,
}
impl Default for Config {
fn default() -> Self {
println!(" Config::default() called");
Config { port: 80, host: "localhost".into() }
}
}
fn main() {
// ...
for s in ["{}", r#"{"port":8080,"host":"h"}"#] {
println!("{s}");
println!(" => {:?}", serde_json::from_str::<Config>(s));
}
}
{}
Config::default() called
=> Ok(Config { port: 80, host: "localhost" })
{"port":8080,"host":"h"}
Config::default() called
=> Ok(Config { port: 8080, host: "h" })
Default 便宜、没有副作用时这无所谓;如果它读环境变量、打开文件、打日志,或者分配一大块内存,每次反序列化都会做一遍。这时改用字段级的 default 或 default = "path":它们是 match 里 None 分支上的表达式,字段在的时候不求值。生成代码的样子见生成的 Deserialize 的「default:容器与字段」。
{} 报的是哪一个?平台 M2 Max、macOS、rustc 1.99.0。src/bin/missing_quiz.rs:
#[derive(Deserialize, Debug)]
struct Maybe(Option<u32>);
#[derive(Deserialize, Debug)]
#[serde(transparent)]
struct MaybeT(Option<u32>);
#[derive(Deserialize, Debug)]
struct S {
a: Option<u32>,
b: Box<Option<u32>>,
c: Option<Maybe>,
d: MaybeT,
e: Maybe,
}
fn main() {
for json in ["{}", r#"{"e":null}"#] {
println!("{json:<10} => {:?}", serde_json::from_str::<S>(json));
}
}
生成的代码按声明顺序处理缺失的字段,遇到第一个报错就返回。{} 报的是 missing field `?` ,? 是哪个字母?第二行 {"e":null} 是 Ok 还是 Err?
怎么确认没中招
- 搜
deserialize_with、with =:每个Option字段旁边有没有default。用#[serde_as]时,as类型不是Option<...>的那些也要自己加。 - 搜字段类型是 newtype、
Value、Vec、HashMap却希望「可以不写」的字段。 - 对每个结构体跑一次
from_str("{}")(或只含必填字段的最小输入),看报出来的是不是你认为的必填字段。这比读代码可靠:表里每一行都是这样跑出来的。 - 容器级
default:看impl Default里有没有昂贵或带副作用的操作。
动脑筋答案
{} => Err(Error("missing field `e`", line: 1, column: 2))
{"e":null} => Ok(S { a: None, b: None, c: None, d: MaybeT(None), e: Maybe(None) })
a:字面上的Option,None。b:Box<T>的Deserialize是forwarded_impl!生成的,直接T::deserialize(deserializer)再Box::new,于是Option第一个调的仍是deserialize_option,None。c:最外层是Option,None;里面的Maybe根本没被读。d:transparent直接读里面的Option,MaybeT(None)。e:derive 的 newtype 先调deserialize_newtype_struct,被转给deserialize_any,报missing field `e`。- 第二行
Ok:字段在、值是null时,serde_json 的deserialize_newtype_struct把自己交给visit_newtype_struct,里面的Option再调deserialize_option,看到null得到None。同一个类型,「写了null」能读,「没写」不能读。
小结
missing field `x`的文字来自de::Error::missing_field的默认实现,位置是对象结尾。先确认报的名字和输入里的键对得上(rename_all)。- 不加
default时,缺失的字段交给MissingFieldDeserializer,只有deserialize_option得到None;newtype、Value、Vec都报错。Box<Option<T>>、#[serde(transparent)]能透过去。 deserialize_with的字段缺失时直接报错,不调你的函数(#2878 未关闭);serde_with 的serde_as只在两边都是Option时自动补default。- 修法是字段级
#[serde(default)];容器级default每次反序列化都无条件调一次Default::default()。 - 这套规则从 0.9.0(#702、#710)起由类型决定,和格式无关。