How can I merge two JSON objects with Rust?
Asked Answered
S

3

12

I have two JSON files:

JSON 1

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

JSON 2

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

I want to merge #2 into #1 where #2 overrides #1, producing following output:

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

I checked out the crate json-patch which does this but it does not compile against stable Rust. Is it possible to do something similar with something like serde_json and stable Rust?

Spathose answered 2/11, 2017 at 8:40 Comment(0)
S
6

Placing the answer suggested by Shepmaster below

#[macro_use]
extern crate serde_json;

use serde_json::Value;

fn merge(a: &mut Value, b: Value) {
    match (a, b) {
        (a @ &mut Value::Object(_), Value::Object(b)) => {
            let a = a.as_object_mut().unwrap();
            for (k, v) in b {
                merge(a.entry(k).or_insert(Value::Null), v);
            }
        }
        (a, b) => *a = b,
    }
}

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);
}
Spathose answered 6/11, 2017 at 17:2 Comment(1)
This algorithm is not correct since it does not delete keys whose value is null in the patch. v.is_null() needs to be checked before the recursive call to mergeWhiffen
W
10

Since you wanted to use json-patch, I assume you were looking specifically for a JSON Merge Patch (RFC 7396) implementation since that is what that crate implements. In that case, merging an object should unset those keys whose corresponding value in the patch is null, which the code samples in the other answers do not implement.

The code that accounts for that is below. I modified the patch to delete the person.lastName key by setting it to null as a demonstration. It also does not need to unwrap() the Option returned by as_object_mut(), unlike one of the other answers.

use serde_json::{json, Value};

fn merge(a: &mut Value, b: Value) {
    if let Value::Object(a) = a {
        if let Value::Object(b) = b {
            for (k, v) in b {
                if v.is_null() {
                    a.remove(&k);
                }
                else {
                    merge(a.entry(k).or_insert(Value::Null), v);
                }
            } 

            return;
        }
    }

    *a = b;
}

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",
            "lastName": null
        },
        "cities":[ "colombo" ]
    });

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

The expected output is

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

Notice person.lastName has been unset.

Whiffen answered 9/1, 2019 at 21:14 Comment(0)
S
6

Placing the answer suggested by Shepmaster below

#[macro_use]
extern crate serde_json;

use serde_json::Value;

fn merge(a: &mut Value, b: Value) {
    match (a, b) {
        (a @ &mut Value::Object(_), Value::Object(b)) => {
            let a = a.as_object_mut().unwrap();
            for (k, v) in b {
                merge(a.entry(k).or_insert(Value::Null), v);
            }
        }
        (a, b) => *a = b,
    }
}

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);
}
Spathose answered 6/11, 2017 at 17:2 Comment(1)
This algorithm is not correct since it does not delete keys whose value is null in the patch. v.is_null() needs to be checked before the recursive call to mergeWhiffen
S
2

This worked for me

#[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);
}
Spathose answered 3/11, 2017 at 19:26 Comment(4)
Seems like if you changed to fn merge(a: Value, b: Value) -> Value you could avoid the cloning.Tercet
@Tercet Could you add an answer here incorporating your suggestion above? I'm new to Rust and do not quite get what you are saying yet. I'll accept your answer if you do.Spathose
Something like this, although I'm a little sad that the unwrap is needed.Tercet
Is there a way to take two immutable references and return a new json object?Minnaminnaminnie

© 2022 - 2025 — McMap. All rights reserved.