How to keep a reference to nodes of serde_json tree

I have a Rust problem and would like some advice: I use serde_json to create a json structure dynamically in a loop. Every iteration creates one Value:Object node and adds it as a sibling to the previous node. Sometimes however (decided at runtime) I need to add the data from the current run as a child to the node created in the previous iteration.

I try to keep a reference to the node of the previous iteration and because I need to add children it needs to be mutable. This conflicts with the reference to the node in my json structure.

I tried to wrap the node into Rc RefCell but that seems to move issue from compile time to runtime. How would I solve this? Any hints appreciated!

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()));
    return 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);
  }
  
  // First iteration
  {
    let node = node("1");
    parent.push(Value::Object(node));
  }
  
  // Second iteration
  {
    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;
      }
    }
  }
  
  // Third iteration
  {
    let node = node("3");
    //parent.push(Value::Object(node));
  }
  
  
  
  let json = serde_json::to_string_pretty(&*root.borrow()).unwrap();
  println!("{}", json);
}

(Playground)

Thank you for the playground link :heart: but it would be better if it failed to compile immediately when I press the Run button... instead of wondering what the problem is and tracking down where the broken part is commented out.

I believe this is exactly what "Problem Case 3" covers in Non-lexical lifetimes: introduction. And indeed, RUSTFLAGS='-Z polonius' cargo +nightly build accepts this code as-is.

The issue with the NLL borrow checker is that you are "threading" the &'a mut T lifetime through conditional control flow to update what parent references, here:

    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;
      }
    }

At the parent=array assignment, array borrows from obj which borrows from parent. Once you do this "lifetime threading" through conditional control flow, you are telling the borrow checker that it needs to "extend" the lifetime of the first parent to its last use.

That's how you end up with "cannot borrow *parent as mutable more than once at a time". It cannot determine at compile time what exactly parent references, and a good-enough-for-most-cases decision is made to extend the lifetime and reject code like this.

If you don't want to use polonius, you might be able to solve this by constructing the tree bottom-up instead of top-down.

3 Likes

I think it's slightly different, though related -- I think it's because you're using a mut &mut _, which has a single, statically typed lifetime. So when you assign

parent = array

array must have the same lifetime (or longer), which means that when you did

if let Some(Value::Object(obj)) = parent.last_mut() {

you had to reborrow parent for the whole of its lifetime, and thus nothing else can use parent again.


What's a workaround? Allow the lifetimes to be different by assigning to a new variable.

    let parent = {
        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") {
                array
            } else {
                parent
            }
        } else {
            parent
        }
    };
4 Likes

This is the salient part that I meant to express! Thank you for articulating that better.

2 Likes

Works great and I learned a lot researching NLLs.

I had asked the same question on Stackoverflow but didn't get a helpful answer there, so I posted yours (giving you credit in the text). If you are active on Stackoverflow as well and want to post your answer under your own account, I'll delete mine.

I didn't want to use nightly so I ultimately used your solution.
Thanks a lot!

1 Like

You are right, I should not have commented out the line that caused the error.
Your suggestion using nightly and polonius works brilliantly. I didn't know about polonius and learned a lot reading about it. Thanks a lot!

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.