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

使用serde_json构建嵌套Map时遇可变引用错误求助

问题:构建嵌套JSON时的Rust引用与借用错误

目标JSON结构

期望生成的JSON如下:

{
    "j1": {
        "1981": {
            "income": 215
        },
        "1982": {
            "income": 350
        }
    },
    "j2": {
        "1981": {
            "income": 100
        },
        "1982": {
            "income": 250
        }
    }
}

现有代码

use serde_json::{Value, Map};

struct s {
    name: String,
    year: i32,
    income: i64
}

fn main() {
    let rows :Vec<s> = vec!(
        s { name: "j1".to_string(), year: 1981, income: 100},
        s { name: "j1".to_string(), year: 1981, income: 115},
        s { name: "j1".to_string(), year: 1982, income: 120},
        s { name: "j1".to_string(), year: 1982, income: 100},
        s { name: "j1".to_string(), year: 1982, income: 130},
        s { name: "j2".to_string(), year: 1981, income: 100},
        s { name: "j2".to_string(), year: 1982, income: 120},
        s { name: "j2".to_string(), year: 1982, income: 130}
    );
    
    let mut data_map: Map<String, Value> = Map::new();
    for row in rows {
        let  mut name: &Map<String, Value> = match data_map.get(&row.name) {
            Some(val) => { &val.as_object().unwrap() },
            None => { &Map::new() }
        };
        let mut year:  &Map<String, Value> = match data_map.get(&row.year.to_string()) {
            Some(val) => { &val.as_object().unwrap() },
            None => { &Map::new() }
        };
        let income: i64 = year.get("income").unwrap_or(&Value::Number(0i64.into())).as_i64().unwrap() + row.income;
        year.insert("income".to_string(), Value::Number(income.into()));
    }
}

错误信息

error[E0596]: cannot borrow `*year` as mutable, as it is behind a `&` reference
  --> src\main.rs:32:9
   |
32 |         year.insert("income".to_string(), Value::Number(income.into()));
   |         ^^^^ `year` is a `&` reference, so the data it refers to cannot be borrowed as mutable
   |
help: consider changing this binding's type
   |
27 |         let mut year: &mut serde_json::Map<std::string::String, Value> = match data_map.get(&row.year.to_string()) {
   |                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

问题分析

原代码存在几个核心问题:

  • 层级逻辑错误:data_map的设计是name -> {year -> {income: ...}}的嵌套结构,但代码直接从data_map中获取year对应的项,完全搞错了层级关系。
  • 不可变引用无法修改:get方法返回的是不可变引用,无法对其指向的Map执行insert等修改操作。
  • 临时引用悬空:当data_map中没有对应name时,返回的是临时创建的Map::new()的引用,该临时对象会在match结束后销毁,导致引用悬空,属于未定义行为。
  • 不安全的类型转换:频繁使用as_object().unwrap(),如果遇到非对象类型会直接panic,风险极高。

修正后的代码

使用Rust的Entry API可以安全高效地处理嵌套Map的插入与更新,彻底解决引用问题:

use serde_json::{json, Map, Value};

#[derive(Debug)]
struct Record {
    name: String,
    year: i32,
    income: i64,
}

fn main() {
    let rows = vec![
        Record { name: "j1".into(), year: 1981, income: 100 },
        Record { name: "j1".into(), year: 1981, income: 115 },
        Record { name: "j1".into(), year: 1982, income: 120 },
        Record { name: "j1".into(), year: 1982, income: 100 },
        Record { name: "j1".into(), year: 1982, income: 130 },
        Record { name: "j2".into(), year: 1981, income: 100 },
        Record { name: "j2".into(), year: 1982, income: 120 },
        Record { name: "j2".into(), year: 1982, income: 130 },
    ];

    let mut data_map: Map<String, Value> = Map::new();

    for row in rows {
        // 获取或插入name对应的年份Map,返回可变引用
        let year_map = data_map
            .entry(row.name)
            .or_insert_with(|| Value::Object(Map::new()))
            .as_object_mut()
            .unwrap(); // 此处unwrap安全,因为我们插入的是Object类型

        // 获取或插入年份对应的收入对象,返回可变引用
        let income_entry = year_map
            .entry(row.year.to_string())
            .or_insert_with(|| json!({ "income": 0 }))
            .as_object_mut()
            .unwrap();

        // 安全累加收入:获取当前值(默认0)后相加
        let current_income = income_entry
            .get("income")
            .and_then(|v| v.as_i64())
            .unwrap_or(0);
        income_entry.insert("income".into(), Value::Number((current_income + row.income).into()));
    }

    // 转换为格式化JSON并打印
    let result = Value::Object(data_map);
    println!("{}", serde_json::to_string_pretty(&result).unwrap());
}

关键说明

  • Entry API的优势:entry(key).or_insert_with(...)可以原子性地检查键是否存在,不存在则插入默认值,同时返回可变引用,避免了不可变引用的修改限制。
  • 层级结构对齐:先处理name对应的年份Map,再在该Map内处理具体年份的收入对象,完全匹配目标JSON的嵌套结构。
  • 类型安全保障:通过or_insert_with插入预期的Object类型,as_object_mut()的unwrap不会触发panic,避免了原代码的不安全操作。
  • 安全的数值处理:用and_then和unwrap_or替代强制unwrap,确保即使数据异常也不会直接崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 13:45:14