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

如何在Rust中高效反序列化多态JSON至目标映射结构?

问题描述

我有如下结构的JSON:

[  
  {
    "id": 123,
    "type": "a",
    "field1": "...",
    ...
  },
  {
    "id": 456,
    "type": "b",
    "field2": "...",
    ...
  },
  ...
]

我希望将其反序列化至一个把不同类型元素存储在不同映射中的结构。目前我通过serde_json先将其解析为单一多态结构实现了需求:

#[derive(Deserialize)]
struct A {
  field1: String,
  ...
}

#[derive(Deserialize)]
struct B {
  field2: String,
  ...
}

#[derive(Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
enum ElementType {
  A(A),
  B(B),
}

#[derive(Deserialize)]
struct Element {
    id: u64,
    #[serde(flatten)]
    type_: ElementType,
}

随后我将该结构转换为目标格式:

struct Document {
  a_fields: HashMap<u64, A>,
  b_fields: HashMap<u64, B>,
}

impl From<Vec<Element>> for Document {
    fn from(input: Vec<Element>) -> Document {
        let mut result = Document{ a_fields : HashMap::new(), b_fields : HashMap::new() };
        for element in input.into_iter() {
            match element.type_ {
                ElementType::A(a) => {
                    result.a_fields.insert(element.id, a);
                }
                ElementType::B(b) => {
                    result.b_fields.insert(element.id, b);
                }
            }
        }
        result
    }
}

这种方式可行但效率不高,因为需要先将整个JSON读入一个结构,再转换至另一个结构。请问直接将JSON反序列化至Document的最优方法是什么?

解决方案

可以为Document实现自定义的Deserialize trait,借助serde的访问器(Visitor)直接遍历JSON数组,逐个处理元素并插入对应哈希表,彻底避免中间结构的内存开销和二次拷贝。

基础高效实现

这种实现复用A、B已派生的Deserialize逻辑,代码简洁且性能足够应对大多数场景:

use serde::de::{self, Deserialize, Deserializer, Visitor};
use serde_json::Value;
use std::collections::HashMap;
use std::fmt;

#[derive(Debug, Deserialize)]
struct A {
    field1: String,
    // 其他字段
}

#[derive(Debug, Deserialize)]
struct B {
    field2: String,
    // 其他字段
}

#[derive(Debug, Default)]
struct Document {
    a_fields: HashMap<u64, A>,
    b_fields: HashMap<u64, B>,
}

impl<'de> Deserialize<'de> for Document {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct DocumentVisitor;

        impl<'de> Visitor<'de> for DocumentVisitor {
            type Value = Document;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("JSON数组,每个元素需包含id和type字段")
            }

            fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
            where
                V: de::SeqAccess<'de>,
            {
                let mut doc = Document::default();

                while let Some(element) = seq.next_element::<Value>()? {
                    // 解析id字段
                    let id = element.get("id")
                        .and_then(|v| v.as_u64())
                        .ok_or_else(|| de::Error::missing_field("id"))?;

                    // 解析type字段
                    let element_type = element.get("type")
                        .and_then(|v| v.as_str())
                        .ok_or_else(|| de::Error::missing_field("type"))?;

                    // 根据类型反序列化并插入对应哈希表
                    match element_type {
                        "a" => {
                            let a: A = serde_json::from_value(element)
                                .map_err(de::Error::custom)?;
                            doc.a_fields.insert(id, a);
                        }
                        "b" => {
                            let b: B = serde_json::from_value(element)
                                .map_err(de::Error::custom)?;
                            doc.b_fields.insert(id, b);
                        }
                        t => return Err(de::Error::custom(format!("未知类型: {}", t))),
                    }
                }

                Ok(doc)
            }
        }

        deserializer.deserialize_seq(DocumentVisitor)
    }
}

// 使用示例
fn main() -> Result<(), serde_json::Error> {
    let json_str = r#"
    [
        {"id": 123, "type": "a", "field1": "foo"},
        {"id": 456, "type": "b", "field2": "bar"}
    ]
    "#;

    let doc: Document = serde_json::from_str(json_str)?;
    println!("{:?}", doc);
    Ok(())
}

极致性能优化版本

如果处理超大型JSON数据,可进一步避免Value的内存分配,直接遍历元素的键值对,边解析边收集对应类型的字段:

use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor};
use serde_json::Value;
use std::collections::HashMap;
use std::fmt;

#[derive(Debug, Deserialize)]
struct A {
    field1: String,
    // 其他字段
}

#[derive(Debug, Deserialize)]
struct B {
    field2: String,
    // 其他字段
}

#[derive(Debug, Default)]
struct Document {
    a_fields: HashMap<u64, A>,
    b_fields: HashMap<u64, B>,
}

impl<'de> Deserialize<'de> for Document {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct DocumentVisitor;

        impl<'de> Visitor<'de> for DocumentVisitor {
            type Value = Document;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("JSON数组,每个元素需包含id和type字段")
            }

            fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
            where
                V: de::SeqAccess<'de>,
            {
                let mut doc = Document::default();

                while let Some(map) = seq.next_element::<de::MapAccess<'de>>()? {
                    visit_element(map, &mut doc)?;
                }

                Ok(doc)
            }
        }

        deserializer.deserialize_seq(DocumentVisitor)
    }
}

// 手动解析单个元素的键值对
fn visit_element<'de, V>(mut map: V, doc: &mut Document) -> Result<(), V::Error>
where
    V: MapAccess<'de>,
{
    let mut id: Option<u64> = None;
    let mut element_type: Option<&str> = None;
    let mut a_data = HashMap::new();
    let mut b_data = HashMap::new();

    while let Some((key, value)) = map.next_entry::<&str, Value>()? {
        match key {
            "id" => id = Some(value.as_u64().ok_or_else(|| de::Error::invalid_value(de::Unexpected::Value(value), &"u64类型的id"))?),
            "type" => element_type = Some(value.as_str().ok_or_else(|| de::Error::invalid_value(de::Unexpected::Value(value), &"字符串类型的type"))?),
            _ => {
                // 根据已解析的type决定字段存入哪个集合
                match element_type {
                    Some("a") => { a_data.insert(key.to_string(), value); }
                    Some("b") => { b_data.insert(key.to_string(), value); }
                    Some(t) => return Err(de::Error::custom(format!("未知类型: {}", t))),
                    None => return Err(de::Error::custom("type字段需在其他字段之前出现")),
                }
            }
        }
    }

    let id = id.ok_or_else(|| de::Error::missing_field("id"))?;
    let element_type = element_type.ok_or_else(|| de::Error::missing_field("type"))?;

    match element_type {
        "a" => {
            let a: A = serde_json::from_value(Value::Object(a_data))
                .map_err(de::Error::custom)?;
            doc.a_fields.insert(id, a);
        }
        "b" => {
            let b: B = serde_json::from_value(Value::Object(b_data))
                .map_err(de::Error::custom)?;
            doc.b_fields.insert(id, b);
        }
        _ => unreachable!(),
    }

    Ok(())
}

实现说明

  • 基础版本:通过Value中转,复用A、B的自动反序列化逻辑,代码简洁,适合常规场景。
  • 极致优化版本:直接遍历键值对,避免整个元素的Value分配,内存占用更低,适合处理GB级别的大型JSON数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 01:07:54