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

Rust中如何用serde_json高效提取嵌套JSON的指定字段?

在Rust中用Serde扁平化解析嵌套JSON的方案

完全可以实现你要的效果——不用写繁琐的多层结构体,同时保留类型安全。核心是利用Serde的自定义反序列化能力,或者用简化的嵌套结构体配合转换,下面是两种实用方案:

方案一:自定义反序列化函数(完全扁平化)

直接在目标结构体上通过#[serde(deserialize_with)]指定路径提取函数,跳过所有无关层级,只解析你需要的字段:

use serde::Deserialize;
use serde_json::Value;
use serde::de::{Deserializer, Error};

// 最终要得到的扁平化类型
#[derive(Debug, Deserialize)]
struct ExtractedData {
    // 从contentJsons.anchors[0].name提取
    #[serde(deserialize_with = "extract_anchor_name")]
    anchor_name: String,
    // 从contentJsons.anchors[0].json.elements提取
    #[serde(deserialize_with = "extract_elements")]
    elements: Vec<Value>, // 替换成你实际需要的具体类型
}

// 提取anchor name的自定义函数
fn extract_anchor_name<'de, D>(deserializer: D) -> Result<String, D::Error>
where
    D: Deserializer<'de>,
{
    let root: Value = Deserialize::deserialize(deserializer)?;
    root.get("contentJsons")
        .and_then(|cj| cj.get("anchors"))
        .and_then(|anchors| anchors.as_array())
        .and_then(|anchors| anchors.first())
        .and_then(|anchor| anchor.get("name"))
        .and_then(|name| name.as_str())
        .map(String::from)
        .ok_or_else(|| D::Error::custom("无法从contentJsons.anchors[0].name提取锚点名称"))
}

// 提取elements的自定义函数
fn extract_elements<'de, D>(deserializer: D) -> Result<Vec<Value>, D::Error>
where
    D: Deserializer<'de>,
{
    let root: Value = Deserialize::deserialize(deserializer)?;
    root.get("contentJsons")
        .and_then(|cj| cj.get("anchors"))
        .and_then(|anchors| anchors.as_array())
        .and_then(|anchors| anchors.first())
        .and_then(|anchor| anchor.get("json"))
        .and_then(|json| json.get("elements"))
        .and_then(|elements| elements.as_array())
        .cloned()
        .ok_or_else(|| D::Error::custom("无法从contentJsons.anchors[0].json.elements提取元素"))
}

// 使用示例
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let api_json = r#"{
        "contentJsons": {
            "anchors": [
                {
                    "name": "test_anchor",
                    "json": {
                        "elements": [{"id": 1}, {"id": 2}]
                    }
                }
            ]
        }
    }"#;

    let data: ExtractedData = serde_json::from_str(api_json)?;
    println!("锚点名称: {}", data.anchor_name);
    println!("元素: {:?}", data.elements);
    Ok(())
}

这种方式完全不需要定义任何嵌套结构体,所有层级跳转都在提取函数里完成,反序列化失败时会返回明确的错误信息,保证类型安全。

方案二:简化嵌套结构体+类型转换

如果觉得自定义函数太繁琐,可以定义仅包含所需字段的最小嵌套结构体,再转换为扁平化的目标类型——比写完整嵌套结构简洁得多,同时可读性更好:

use serde::Deserialize;

// 仅定义需要的嵌套层级和字段,忽略无关内容
#[derive(Debug, Deserialize)]
struct RawApiResponse {
    #[serde(rename = "contentJsons")]
    content_jsons: ContentJsons,
}

#[derive(Debug, Deserialize)]
struct ContentJsons {
    anchors: Vec<Anchor>,
}

#[derive(Debug, Deserialize)]
struct Anchor {
    name: String,
    #[serde(rename = "json")]
    anchor_json: AnchorJson,
}

#[derive(Debug, Deserialize)]
struct AnchorJson {
    elements: Vec<Element>, // 替换成你实际需要的类型
}

// 你最终要使用的扁平化类型
#[derive(Debug)]
struct ExtractedData {
    anchor_name: String,
    elements: Vec<Element>,
}

#[derive(Debug, Deserialize)]
struct Element {
    id: u32,
}

// 实现从原始响应到扁平化类型的转换
impl From<RawApiResponse> for ExtractedData {
    fn from(raw: RawApiResponse) -> Self {
        let first_anchor = raw.content_jsons.anchors.into_iter()
            .next()
            .expect("API返回的JSON中没有anchors数据");
        
        Self {
            anchor_name: first_anchor.name,
            elements: first_anchor.anchor_json.elements,
        }
    }
}

// 使用示例
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let api_json = r#"{
        "contentJsons": {
            "anchors": [
                {
                    "name": "test_anchor",
                    "json": {
                        "elements": [{"id": 1}, {"id": 2}]
                    }
                }
            ]
        }
    }"#;

    let raw_data: RawApiResponse = serde_json::from_str(api_json)?;
    let extracted = ExtractedData::from(raw_data);
    println!("锚点名称: {}", extracted.anchor_name);
    println!("元素: {:?}", extracted.elements);
    Ok(())
}

这种方式的优势是类型定义更清晰,后续如果需要新增字段,扩展起来更方便,同样能保证类型安全。

可选:用serde_path_to_error定位反序列化错误

如果担心反序列化时无法定位问题,可以引入serde_path_to_error crate,它会返回错误发生的具体路径,方便调试:

use serde_path_to_error::Deserializer as PathDeserializer;

// 在反序列化时包裹一层路径解析器
let deserializer = serde_json::Deserializer::from_str(api_json);
let path_deserializer = PathDeserializer::new(deserializer);

match ExtractedData::deserialize(path_deserializer) {
    Ok(data) => println!("{:?}", data),
    Err(e) => println!("错误路径: {}, 错误信息: {}", e.path(), e.inner()),
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 05:05:16