You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何使用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(())
}

注意事项

  1. 手动处理方案的示例代码仅处理了顶层字段的引用,你可以扩展逻辑为递归遍历所有Value节点,支持嵌套的$ref引用。
  2. 使用第三方库时,建议查看库的官方文档确认功能细节和维护状态。
  3. 确保引用的文件相对路径正确,相对路径默认基于程序运行的工作目录,你也可以在代码中手动指定基础路径。

火山引擎 最新活动