Json: Can I merge two JSON objects using this library?

Created on 2 Nov 2017  路  4Comments  路  Source: serde-rs/json

Let's say I have the JSON below (JSON 1)

{
  "title": "This is a title",
  "person" : {
    "firstName" : "John",
    "lastName" : "Doe"
  },
  "cities":[ "london", "paris" ]
}

And another as follows (JSON 2)

{
  "title": "This is another title",
  "person" : {
    "firstName" : "Jane"
  },
  "cities":[ "colombo" ]
}

How can I get the following output? (merging 2 into 1 where 2 Overrides 1)

{
  "title": "This is another title",
  "person" : {
    "firstName" : "Jane",
    "lastName" : "Doe"
  },
  "cities":[ "colombo" ]
}

I did checkout the crate json-patch which does this but it does not compile against stable rust. I want to know if its possible to do something similar with serde.

support

Most helpful comment

Here is one possible implementation. I haven't tested it thoroughly but it works for your example.

#[macro_use]
extern crate serde_json;

use serde_json::Value;

fn merge(a: &mut Value, b: &Value) {
    match (a, b) {
        (&mut Value::Object(ref mut a), &Value::Object(ref b)) => {
            for (k, v) in b {
                merge(a.entry(k.clone()).or_insert(Value::Null), v);
            }
        }
        (a, b) => {
            *a = b.clone();
        }
    }
}

fn main() {
    let mut a = json!({
        "title": "This is a title",
        "person" : {
            "firstName" : "John",
            "lastName" : "Doe"
        },
        "cities":[ "london", "paris" ]
    });

    let b = json!({
        "title": "This is another title",
        "person" : {
            "firstName" : "Jane"
        },
        "cities":[ "colombo" ]
    });

    merge(&mut a, &b);
    println!("{:#}", a);
}

All 4 comments

If you parse both pieces of JSON to the serde_json::Value enum then you can call as_object_mut or use if let to destructure the values to Maps. You can then use extend on the first Map with the second Map as the argument. I believe that should result in a Map instance that matches what you describe. You can then construct a new Value::Object(map1) and serde_json::to_string or to_writer with that Value.

Here is one possible implementation. I haven't tested it thoroughly but it works for your example.

#[macro_use]
extern crate serde_json;

use serde_json::Value;

fn merge(a: &mut Value, b: &Value) {
    match (a, b) {
        (&mut Value::Object(ref mut a), &Value::Object(ref b)) => {
            for (k, v) in b {
                merge(a.entry(k.clone()).or_insert(Value::Null), v);
            }
        }
        (a, b) => {
            *a = b.clone();
        }
    }
}

fn main() {
    let mut a = json!({
        "title": "This is a title",
        "person" : {
            "firstName" : "John",
            "lastName" : "Doe"
        },
        "cities":[ "london", "paris" ]
    });

    let b = json!({
        "title": "This is another title",
        "person" : {
            "firstName" : "Jane"
        },
        "cities":[ "colombo" ]
    });

    merge(&mut a, &b);
    println!("{:#}", a);
}

@commandline Thank you for your suggestion.
@dtolnay Yours worked perfectly. Thank you!. Any Gotchas in your code I should know about? I'm still learning Rust.

I don't think there are gotchas but depending on how the rest of your code is structured and specifically depending on whether you have ownership of a and/or b, you may be able to come up with a more efficient implementation. For example if you have ownership of b then you may be able to move "This is another title" instead of cloning it.

Was this page helpful?
0 / 5 - 0 ratings