Search code examples
rustyamlserde

how to combine (including nested array values) two serde_yaml::Value objects?


I am having trouble creating a recursive function to parse two serde_yaml::Value variables and combine them. It's easy enough to combine them at a base-level object, but then the sublevel values are only those of the combined value.

Given:

let original:serde_yaml::Value = serde_yaml::from_str(r#"
keyA:
  subKeyA:
    - A
    - B
    - C
keyB: "one"
keyC: "a"
"#
).unwrap();

let add_or_modify_these_values:serde_yaml::Value = serde_yaml::from_str(r#"
keyA:
  subKeyA:
    - D
  subKeyB:
    - BA
keyB: "two"
keyC:
  - A
  - B
"#
).unwrap();

How would I combine them so all nested properties are accounted for, eg:

keyA:
  subKeyA:
    - A
    - B
    - C
    - D
  subKeyB:
    - BA
keyB: "two"
keyC:
  - A
  - B

When there are complications (for example, different value types like keyC) I'd prefer overwriting the original value type with the new one.

Edit: I've also looked at a similar question for json here: How can I merge two JSON objects with Rust? but that merge method will not combine array values, only overwrite.


Solution

  • I've figured out the conversion below (could possibly use some cleanup):

    fn merge_yaml(a: &mut serde_yaml::Value, b: serde_yaml::Value) {
        match (a, b) {
            (a @ &mut serde_yaml::Value::Mapping(_), serde_yaml::Value::Mapping(b)) => {
                let a = a.as_mapping_mut().unwrap();
                for (k, v) in b {
                    if v.is_sequence() && a.contains_key(&k) && a[&k].is_sequence() { 
                        let mut _b = a.get(&k).unwrap().as_sequence().unwrap().to_owned();
                        _b.append(&mut v.as_sequence().unwrap().to_owned());
                        a[&k] = serde_yaml::Value::from(_b);
                        continue;
                    }
                    if !a.contains_key(&k) {a.insert(k.to_owned(), v.to_owned());}
                    else { merge_yaml(&mut a[&k], v); }
    
                }
                
            }
            (a, b) => *a = b,
        }
    }