Rust in Everything
基于 serde 1.0.229

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_optionNone
Maybe(Option<u32>)(derive 的 newtype)deserialize_newtype_struct报错,里面的 Option 没机会读
serde_json::Valuedeserialize_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 仍开着)。

📝NOTE
冷知识:「缺了的 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:容器与字段」。

❗IMPORTANT
动脑筋:五个字段,{} 报的是哪一个?

平台 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)起由类型决定,和格式无关。

💬 关联讨论

发起讨论
加载中...