如何使用 Rust 合并两个 JSON 对象?

11

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" ]
}

我希望将#2合并到#1中,其中#2会覆盖#1,生成以下输出:
{
  "title": "This is another title",
  "person" : {
    "firstName" : "Jane",
    "lastName" : "Doe"
  },
  "cities":[ "colombo" ]
}

我查看了这个创建物json-patch,它可以做到这一点,但它无法在稳定版本的Rust中编译。是否可能使用类似于serde_json和稳定Rust来实现类似的功能?

3个回答

8

由于您想使用json-patch,我认为您特别在寻找JSON合并补丁(RFC 7396)实现,因为这是该软件包实现的内容。在这种情况下,合并对象应取消那些对应补丁中值为null的键,而其他答案中的代码示例没有实现这一点。

考虑到这一点的代码如下所示。我修改了补丁,将person.lastName键设置为null以进行演示。与其他答案之一不同,它也不需要as_object_mut()返回的Option进行unwrap()操作。

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

预期输出是:
{
  "cities": [
    "colombo"
  ],
  "person": {
    "firstName": "Jane"
  },
  "title": "This is another title"
}

注意:person.lastName已被取消设置。


6

以下是Shepmaster建议的答案

#[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);
}

这个算法不正确,因为它没有删除补丁中值为空的键。在递归调用merge之前需要检查v.is_null() - Arnavion

2
这对我起作用了。
#[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);
}

1
看起来如果你改成 fn merge(a: Value, b: Value) -> Value 就可以避免克隆了。 - Shepmaster
@Shepmaster,您能否在此处添加一个答案,包含您上面的建议?我是Rust的新手,还不太明白您在说什么。如果您这样做,我会接受您的答案。 - Harindaka
1
类似这样的东西,虽然需要使用 unwrap,但我还是有点难过。 - Shepmaster
有没有一种方法可以获取两个不可变的引用并返回一个新的JSON对象? - Michael Pacheco

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接