Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I merge two JSON objects with Rust?

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?

like image 500
Harindaka Avatar asked Nov 02 '17 08:11

Harindaka


2 Answers

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.

like image 81
Arnavion Avatar answered Sep 21 '22 00:09

Arnavion


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);
}
like image 34
Harindaka Avatar answered Sep 20 '22 00:09

Harindaka