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

serde自定义反序列化器:如何跟踪HashMap转结构体的解析路径?

问题描述

我需要实现一个Serde自定义反序列化器,将一组以冒号分隔键的键值对HashMap<String, String>反序列化为嵌套结构体。

键值对示例

("device:address:ip", "127.0.0.1"),
("device:address:port", "9001"),
("device:keys:0", "0000"),
("device:keys:1", "1111"),

目标Rust数据模型

struct DeviceAddress {
    ip: String,
    port: u16
}

struct DeviceConfig {
    address: DeviceAddress,
    keys: Vec<String>
}

struct Config {
    device: DeviceConfig
}

Serde遍历模型的流程中,到反序列化具体值(比如ip字段)时,需要拼接出完整路径键(如device:address:ip)去HashMap中查找对应值,但卡在如何在反序列化过程中跟踪当前路径这一步。


解决方案

核心思路是在自定义反序列化器中维护当前路径栈:每进入一个结构体字段或数组索引,就把对应的名称/索引压入栈;退出时弹出。当需要查找值时,把栈中元素用冒号连接成完整键,去HashMap中取值。

步骤1:定义自定义反序列化器结构体

先定义持有HashMap和当前路径的反序列化器:

use serde::{de, Deserialize, Deserializer};
use std::collections::HashMap;
use std::fmt;

struct KvpDeserializer<'a> {
    map: &'a HashMap<String, String>,
    path: Vec<String>,
}

impl<'a> KvpDeserializer<'a> {
    fn new(map: &'a HashMap<String, String>) -> Self {
        KvpDeserializer {
            map,
            path: Vec::new(),
        }
    }
}

步骤2:实现de::Deserializer trait

重点实现结构体、数组的处理逻辑,同时跟踪路径:

impl<'de, 'a> de::Deserializer<'de> for KvpDeserializer<'a> {
    type Error = de::value::Error;

    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: de::Visitor<'de>,
    {
        // 拼接当前路径为完整键,查找HashMap取值
        let key = self.path.join(":");
        match self.map.get(&key) {
            Some(value) => visitor.visit_str(value),
            None => Err(de::Error::missing_field(&key)),
        }
    }

    // 处理结构体:遍历字段时拼接路径
    fn deserialize_struct<V>(
        self,
        _name: &'static str,
        fields: &'static [&'static str],
        visitor: V,
    ) -> Result<V::Value, Self::Error>
    where
        V: de::Visitor<'de>,
    {
        visitor.visit_map(StructAccess {
            de: self,
            fields: fields.iter().copied(),
        })
    }

    // 处理数组/向量:按索引拼接路径
    fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: de::Visitor<'de>,
    {
        visitor.visit_seq(SeqAccess {
            de: self,
            index: 0,
        })
    }

    // 其他基础类型反序列化委托给deserialize_any
    forward_to_deserialize_any! {
        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
        bytes byte_buf option unit unit_struct newtype_struct tuple
        tuple_struct enum identifier ignored_any
    }
}

步骤3:实现结构体访问器(StructAccess)

访问结构体字段时,将字段名加入当前路径:

struct StructAccess<'a> {
    de: KvpDeserializer<'a>,
    fields: std::slice::Iter<'static, &'static str>,
}

impl<'de, 'a> de::MapAccess<'de> for StructAccess<'a> {
    type Error = de::value::Error;

    fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
    where
        K: de::DeserializeSeed<'de>,
    {
        self.fields.next().map_or(Ok(None), |field| {
            seed.deserialize(KeyDeserializer(field)).map(Some)
        })
    }

    fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
    where
        V: de::DeserializeSeed<'de>,
    {
        let field = self.fields.next_back().unwrap();
        // 压入字段名到路径
        self.de.path.push(field.to_string());
        let value = seed.deserialize(self.de.clone());
        // 退出字段时弹出路径
        self.de.path.pop();
        value
    }
}

// 辅助结构体:反序列化结构体字段名
struct KeyDeserializer<'a>(&'a str);

impl<'de, 'a> de::Deserializer<'de> for KeyDeserializer<'a> {
    type Error = de::value::Error;

    fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: de::Visitor<'de>,
    {
        visitor.visit_str(self.0)
    }

    forward_to_deserialize_identifier! {
        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
        bytes byte_buf option unit unit_struct newtype_struct tuple
        tuple_struct enum struct map seq ignored_any
    }
}

步骤4:实现数组访问器(SeqAccess)

访问数组元素时,将索引加入当前路径:

struct SeqAccess<'a> {
    de: KvpDeserializer<'a>,
    index: usize,
}

impl<'de, 'a> de::SeqAccess<'de> for SeqAccess<'a> {
    type Error = de::value::Error;

    fn next_element_seed<V>(&mut self, seed: V) -> Result<Option<V::Value>, Self::Error>
    where
        V: de::DeserializeSeed<'de>,
    {
        // 压入当前索引到路径
        self.de.path.push(self.index.to_string());
        let key = self.de.path.join(":");
        let result = if self.de.map.contains_key(&key) {
            let value = seed.deserialize(self.de.clone()).map(Some);
            self.index += 1;
            value
        } else {
            Ok(None)
        };
        // 退出元素时弹出索引路径
        self.de.path.pop();
        result
    }
}

步骤5:封装反序列化入口

提供便捷函数完成HashMap到目标结构体的反序列化:

fn from_kvp<T>(map: &HashMap<String, String>) -> Result<T, de::value::Error>
where
    T: Deserialize<'_>,
{
    let de = KvpDeserializer::new(map);
    Deserialize::deserialize(de)
}

使用示例

fn main() {
    let mut map = HashMap::new();
    map.insert("device:address:ip".to_string(), "127.0.0.1".to_string());
    map.insert("device:address:port".to_string(), "9001".to_string());
    map.insert("device:keys:0".to_string(), "0000".to_string());
    map.insert("device:keys:1".to_string(), "1111".to_string());

    let config: Config = from_kvp(&map).unwrap();
    assert_eq!(config.device.address.ip, "127.0.0.1");
    assert_eq!(config.device.address.port, 9001);
    assert_eq!(config.device.keys, vec!["0000".to_string(), "1111".to_string()]);
}

关键说明

  • 路径栈通过push/pop维护,确保嵌套结构体、数组的路径正确性;
  • 结构体字段直接用字段名拼接路径,数组元素用索引字符串拼接路径;
  • 基础类型反序列化统一通过拼接后的路径去HashMap取值并转换。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 11:44:55