这几天在捣鼓Serde::Deserializer以实现自己的反序列化功能,发现它的用法有一点难以理解。死磕了7、8小时后,算是搞明白了。

如果你也想自己捣鼓,你可以试著把下列两个网址所有代码(代码是以1个简单json反序列化的例子来举例说明Deserializer怎么用)复制到Idea或vscode等ide,在想要跟踪的地方设断点或打印信息,然后运行代码中的测试用例,多运行几次就能够逐渐理解Deserializer怎么用:

下面我试著把自己的理解叙述一下,希望能帮助到后来者:


  • 假设我想要透过调用new_instance::<T>()->T,期望它返回T类的实例(T类的字段,凡是数字的设为0,bool设为false,String设为"".to_owned(),其它类则递归地返回它的实例)。则new_instance()代码如下图(1)所示:

Rust Serde 反序列化的使用方法_rust

  • 上图(1)被调用后,运行到T::deserialize(&mut deserializer)后,Deserializer的fn deserialize_struct(fields)首先被调用,如上图(2)所示;在fn deserialize_struct(fields)中可以取到T的struct名称(name: &str)及所有字段名(fields: &[&str]);我第1次尝试到这里就卡壳了;请继续看下去:
  • 接下来,我们希望一个字段一个字段地(不需要按字段顺序)返回该字段的类型和值,怎么办呢?需要借助一个实现de::MapAccess的MapAccesses类,这个类只有两个方法fn next_key_seed() 和 fn next_value_seed();
  • 当执行到visitor.visit_map(MapAccesses::new(self)),会将Deserializer自己传入MapAccesses中,然后反复调用next_key_seed()及next_value_seed(),如上图(3)所示,直到next_key_seed返回Ok(None)停止;
  • 从函数名可以判断出next_key_seed()及next_value_seed()分别要返回字段类型及字段值,问题是它们分别返回泛型K::Value和V::Value,不太容易理解它们是什么!?
  • 如上图,在fn next_key_seed()中主要语句只有一句:seed.deserialize(&mut *).map(Some),暂时忽略map(Some),从seed.deserialize(&mut*self.de)判断它又调回Deserializer了。经打印信息确认,它调用了Deserializer::deserialize_identifier()方法,如上图(4)。在这个方法中你需要执行visitor.visit_string(String),这里参数String为字段名。由于我的目的是返回一个T实例,因此这里我只要将Deserializer::deserialize_struct()获得的字段名依序传给visitor.visit_string即可(上图没有示例,请自行实现);然后又回到next_key_seed()调用seed.deserialize(&mut *)处;
  • 接下来,轮到fn next_value_seed()被调用,顾名思义,要给它Deserializer::deserialize_identifier()方法中传入visitor.visit_string(String)的字段的值。注意,这里seed: V是泛型,从追踪代码结果来看,seed传入的类型显示会因为字段类型不同而不同,因此当执行seed.deserialize(&mut *)时,会根据字段类型调用Deserializer的不同deserialize_xxx()方法(这里xxx根据类型是u32, i32, bool, string.....),如上图(5)。只要在Deserializer.deserialize_xxx()所有方法中分别调用visitor.visit_xxx(v)(例如visitor.visit_u32(0),请同理类推)即可;
  • 然后又回到调用fn next_key_seed(),直到next_key_seed()返回Ok(None)为止;至此,一个反序列化struct的过程结束;

上面这个例子只说明deserialize_struct()的处理方法,在​​https://serde.rs/impl-deserializer.html​​的示例代码中可以看到如何处理deserialize_seq()和deserialize_enum()的示例,和deserialize_struct的原理差不多,请自行参考示例代码,不再介绍。