You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

反序列化时字段为何被多次访问?Serde_test测试问题排查

问题:serde中visit_map被调用两次的原因?

我在运行serde_test,测试官方文档里的示例代码,只自行编写了fn main()函数。我的目标是验证visit_map的调用符合预期——每个结构体仅被调用一次,但测试发现每个字段被调用了两次,这是为什么?

目前没有文档说明visit_map会被调用两次,作为用户有理由认为只会调用一次。如果要在visit_map中执行复杂操作,手动实现Deserialize的成本会很高。

测试代码

use std::fmt;

use serde::de::{self, Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};

#[derive(Debug, PartialEq)]
struct Duration {
    secs: u64,
    nanos: u32,
}

impl<'de> Deserialize<'de> for Duration {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Debug)]
        enum Field {
            Secs,
            Nanos,
        }

        // This part could also be generated independently by:
        //
        //    #[derive(Deserialize)]
        //    #[serde(field_identifier, rename_all = "lowercase")]
        //    enum Field { Secs, Nanos }
        impl<'de> Deserialize<'de> for Field {
            fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
            where
                D: Deserializer<'de>,
            {
                struct FieldVisitor;

                impl<'de> Visitor<'de> for FieldVisitor {
                    type Value = Field;

                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                        formatter.write_str("`secs` or `nanos`")
                    }

                    fn visit_str<E>(self, value: &str) -> Result<Field, E>
                    where
                        E: de::Error,
                    {
                        match value {
                            "secs" => Ok(Field::Secs),
                            "nanos" => Ok(Field::Nanos),
                            _ => Err(de::Error::unknown_field(value, FIELDS)),
                        }
                    }
                }

                deserializer.deserialize_identifier(FieldVisitor)
            }
        }

        struct DurationVisitor;

        impl<'de> Visitor<'de> for DurationVisitor {
            type Value = Duration;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("struct Duration")
            }

            fn visit_seq<V>(self, mut seq: V) -> Result<Duration, V::Error>
            where
                V: SeqAccess<'de>,
            {
                let secs = seq
                    .next_element()?
                    .ok_or_else(|| de::Error::invalid_length(0, &self))?;
                let nanos = seq
                    .next_element()?
                    .ok_or_else(|| de::Error::invalid_length(1, &self))?;
                Ok(Duration { secs, nanos })
            }

            fn visit_map<V>(self, mut map: V) -> Result<Duration, V::Error>
            where
                V: MapAccess<'de>,
            {
                let mut secs = None;
                let mut nanos = None;
                while let Some(key) = map.next_key()? {
                    println!(" Found Key {:?}", key);
                    match key {
                        Field::Secs => {
                            if secs.is_some() {
                                return Err(de::Error::duplicate_field("secs"));
                            }
                            secs = Some(map.next_value()?);
                        }
                        Field::Nanos => {
                            if nanos.is_some() {
                                return Err(de::Error::duplicate_field("nanos"));
                            }
                            nanos = Some(map.next_value()?);
                        }
                    }
                }
                let secs = secs.ok_or_else(|| de::Error::missing_field("secs"))?;
                let nanos = nanos.ok_or_else(|| de::Error::missing_field("nanos"))?;
                Ok(Duration { secs, nanos })
            }
        }

        const FIELDS: &'static [&'static str] = &["secs", "nanos"];
        deserializer.deserialize_struct("Duration", FIELDS, DurationVisitor)
    }
}

fn main() {
    use serde_test::Token;
    let obj = Duration {
        secs: 10,
        nanos: 12,
    };
    serde_test::assert_de_tokens(
        &obj,
        &[
            Token::Struct {
                name: "Duration",
                len: 3,
            },
            Token::Str("secs"),
            Token::U64(10),
            Token::Str("nanos"),
            Token::U64(12),
            // the following fields can be omitted, possibly because of visit_str is a
            // function that would be called on demand, that is, as long as some Token
            // is being met. That leads to a unusual result: incomplete group of
            // matched tokens is acceptable!
            Token::StructEnd,
        ],
    );
}

运行输出

$ cargo run
 Found Key Secs
 Found Key Nanos
 Found Key Secs
 Found Key Nanos

原因分析及解决办法

原因

serde_test的assert_de_tokens默认会执行两次反序列化:

  1. 第一次是正常反序列化,验证结果是否与预期对象一致;
  2. 第二次是兼容性校验,验证反序列化逻辑是否能正确处理包含未知字段的输入(即使测试用例里没有未知字段,这个校验步骤依然会触发)。

这两次反序列化都会调用visit_map,所以会看到每个字段的输出重复出现。

解决办法

如果只需要单次反序列化验证,直接用assert_de_tokens_single替代assert_de_tokens即可。同时注意测试用例中Token::Struct的len参数写了3,但实际只有2个字段,建议改成正确的2:

修改后的main函数:

fn main() {
    use serde_test::Token;
    let obj = Duration {
        secs: 10,
        nanos: 12,
    };
    serde_test::assert_de_tokens_single(
        &obj,
        &[
            Token::Struct {
                name: "Duration",
                len: 2,
            },
            Token::Str("secs"),
            Token::U64(10),
            Token::Str("nanos"),
            Token::U64(12),
            Token::StructEnd,
        ],
    );
}

运行修改后的代码,就只会看到一次字段输出了。


内容的提问来源于stack exchange,提问作者absuu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 23:52:02