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
相关产品推荐
相关产品推荐

