如何在Rust中动态构建serde_json树并保留节点引用?
Rust中使用serde_json动态构建层级JSON结构的解决方案
问题背景
在Rust开发中,使用serde_json在循环里动态创建JSON结构:每次迭代默认生成一个Value::Object节点作为前一节点的同级,但有时需要将当前节点设为前一节点的子节点。尝试保留前一节点的可变引用时,会和JSON结构内的节点引用冲突;用Rc<RefCell>包装节点,只是把编译时问题转移到运行时,需要更合理的解决方式。
预期JSON输出
{ "children": [ { "name": "name1" }, { "name": "name2", "children": [ { "name": "name3" } ] }, { "name": "name4" } ] }
对应输入结构
- name1 - name2 - name3 - name4
简化示例代码
use serde_json::{Value, Map}; use std::borrow::Borrow; fn node(name: &str) -> Map<String, Value> { let mut node: Map<String, Value> = Map::new(); node.insert("name".to_string(), Value::String(name.to_string())); node } fn main() { let mut root: Map<String, Value> = Map::new(); root.insert("children".to_string(), Value::Array(Vec::new())); let mut parent: &mut Vec<Value>; if let Some(Value::Array(array)) = root.get_mut("children") { parent = array; } else { std::process::exit(1); } // 第一次迭代 { let node = node("1"); parent.push(Value::Object(node)); } // 第二次迭代 { let node = node("2"); let mut array = Vec::new(); array.push(Value::Object(node)); if let Some(Value::Object(obj)) = parent.last_mut() { obj.insert("children".to_string(), Value::Array(array)); if let Some(Value::Array(array)) = obj.get_mut("children") { parent = array; } } } // 第三次迭代 { let node = node("3"); // parent.push(Value::Object(node)); } let json = serde_json::to_string_pretty(&*root.borrow()).unwrap(); println!("{}", json); }
解决方案:用栈跟踪当前父节点数组
核心思路是用一个栈(Vec<&mut Vec<Value>>)记录当前所在层级的父节点数组,栈的结构天然匹配层级的进入与退出逻辑,完全符合Rust的借用规则,无需运行时检查。
修改后的实现代码
use serde_json::{Value, Map}; fn node(name: &str) -> Map<String, Value> { let mut node = Map::new(); node.insert("name".to_string(), Value::String(name.to_string())); node } fn main() { let mut root = Map::new(); root.insert("children".to_string(), Value::Array(Vec::new())); // 栈顶始终指向当前要添加节点的父数组 let mut parent_stack: Vec<&mut Vec<Value>> = Vec::new(); if let Some(Value::Array(array)) = root.get_mut("children") { parent_stack.push(array); } else { std::process::exit(1); } // 模拟输入结构的迭代流程 // 1. 添加name1(同级) parent_stack.last_mut().unwrap().push(Value::Object(node("name1"))); // 2. 添加name2,进入其子层级 parent_stack.last_mut().unwrap().push(Value::Object(node("name2"))); // 获取刚添加的name2节点,创建children数组并压入栈 if let Some(Value::Object(obj)) = parent_stack.last().unwrap().last_mut() { obj.insert("children".to_string(), Value::Array(Vec::new())); if let Some(Value::Array(array)) = obj.get_mut("children") { parent_stack.push(array); } } // 3. 添加name3(子级) parent_stack.last_mut().unwrap().push(Value::Object(node("name3"))); // 4. 回到父层级(弹出栈顶) parent_stack.pop(); // 5. 添加name4(同级) parent_stack.last_mut().unwrap().push(Value::Object(node("name4"))); // 输出最终JSON let json = serde_json::to_string_pretty(&root).unwrap(); println!("{}", json); }
方案优势
- 编译时安全:所有引用操作都符合Rust的借用规则,无需
Rc<RefCell>带来的运行时开销与panic风险。 - 逻辑清晰:栈的压入/弹出操作完美对应层级的进入/退出,和输入结构的缩进逻辑天然匹配。
- 易于扩展:如果是处理逐行输入的场景,可根据每行缩进深度动态调整栈的状态:
- 缩进加深:压入当前节点的children数组
- 缩进不变:保持栈顶不变
- 缩进变浅:弹出对应次数的栈顶元素
内容的提问来源于stack exchange,提问作者Ludwig Weinzierl
相关产品推荐
相关产品推荐

