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

