如何使用Serde反序列化包含相对路径$ref引用的JSON数据?
如何使用Serde反序列化包含相对路径$ref引用的JSON数据?
错误原因分析
你遇到的panic本质是serde_json本身不支持自动解析JSON Reference(即$ref语法)。当你直接反序列化b.json时,serde_json会把{"$ref": "./a.json#/p_list"}当作一个普通的键值对Map结构处理,但你的A结构体中path_list字段声明的是Vec<PathBuf>(数组/序列类型),类型不匹配就抛出了invalid type: map, expected a sequence的错误。
解决方案
下面提供两种可行的解决思路,你可以根据需求选择:
方案1:手动处理JSON引用(适合理解原理,无额外依赖负担)
我们可以先把JSON解析为动态的serde_json::Value,手动检测并解析$ref引用,再将目标数据反序列化为指定类型。
修改后的main.rs代码:
use std::fs; use std::path::{Path, PathBuf}; use serde::Deserialize; use serde_json::{self, Value}; use jsonptr::JsonPointer; // 需要添加该依赖处理JSON Pointer #[derive(Deserialize, Debug)] struct A { path_list: Vec<PathBuf>, } // 递归处理JSON中的$ref引用 fn load_resolved_json(path: impl AsRef<Path>) -> Result<Value, Box<dyn std::error::Error>> { let json_str = fs::read_to_string(path.as_ref())?; let mut value: Value = serde_json::from_str(&json_str)?; // 遍历所有顶层键值对,处理$ref(可扩展为递归遍历嵌套节点) if let Value::Object(map) = &mut value { for (_, val) in map.iter_mut() { if let Value::Object(ref_map) = val { if let Some(Value::String(ref_str)) = ref_map.get("$ref") { // 拆分引用:文件路径 + JSON Pointer部分 let (file_rel_path, ptr_str) = ref_str.split_once('#').ok_or("无效的$ref格式")?; // 拼接完整文件路径(基于当前文件的父目录) let target_file = path.as_ref().parent().unwrap().join(file_rel_path); // 加载目标文件并解析其引用(递归处理) let target_json = load_resolved_json(target_file)?; // 使用JSON Pointer提取对应节点 let resolved_val = JsonPointer::new(ptr_str)?.query(&target_json)?; // 替换原引用节点为实际数据 *val = resolved_val.clone(); } } } } Ok(value) } fn main() -> Result<(), Box<dyn std::error::Error>> { let resolved_json = load_resolved_json("b.json")?; // 将解析后的完整JSON反序列化为目标结构体 let a_obj: A = serde_json::from_value(resolved_json)?; for p in &a_obj.path_list { println!("{}", p.display()); } Ok(()) }
对应的Cargo.toml需要添加jsonptr依赖:
[package] name = "serde_demo" version = "0.1.0" edition = "2024" [dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" jsonptr = "0.4" # 可替换为最新稳定版本
方案2:使用自动处理引用的第三方库(代码更简洁)
如果你不想手动处理引用逻辑,可以使用支持自动解析$ref的Serde反序列化库,比如serde_json_ref。
修改Cargo.toml添加依赖:
[package] name = "serde_demo" version = "0.1.0" edition = "2024" [dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_json_ref = "0.3" # 确认使用最新稳定版本
修改后的main.rs代码:
use std::fs; use std::path::PathBuf; use serde::Deserialize; use serde_json_ref::Deserializer; #[derive(Deserialize, Debug)] struct A { path_list: Vec<PathBuf>, } fn main() -> Result<(), Box<dyn std::error::Error>> { let json_data = fs::read_to_string("b.json")?; // 创建支持引用解析的反序列化器,指定当前工作目录作为相对路径的基础 let mut deserializer = Deserializer::from_str(&json_data) .base_path(std::env::current_dir()?)?; // 直接反序列化为目标结构体,库会自动处理$ref引用 let a_obj: A = A::deserialize(&mut deserializer)?; for p in &a_obj.path_list { println!("{}", p.display()); } Ok(()) }
注意事项
- 手动处理方案的示例代码仅处理了顶层字段的引用,你可以扩展逻辑为递归遍历所有
Value节点,支持嵌套的$ref引用。 - 使用第三方库时,建议查看库的官方文档确认功能细节和维护状态。
- 确保引用的文件相对路径正确,相对路径默认基于程序运行的工作目录,你也可以在代码中手动指定基础路径。




