Как я могу объединить два объекта JSON с Rust?

У меня есть два файла JSON:

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?


person Harindaka    schedule 02.11.2017    source источник


Ответы (3)


Поскольку вы хотели использовать json-patch, я предполагаю, что вы искали конкретно Реализация патча слияния JSON (RFC 7396), которую реализует этот ящик. В этом случае слияние объекта должно отключить те ключи, соответствующее значение которых в патче равно null, что не реализовано в примерах кода в других ответах.

Код, который учитывает это, приведен ниже. Я изменил патч, чтобы удалить ключ person.lastName, установив для него значение null в качестве демонстрации. Также не требуется unwrap() Option, возвращаемый as_object_mut().

#[macro_use]
extern crate serde_json;

use serde_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 a title"
}

Уведомление person.lastName было отключено.

person Arnavion    schedule 09.01.2019

Размещение ответа, предложенного Шепмастером, ниже

#[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);
}
person Harindaka    schedule 06.11.2017
comment
Этот алгоритм неверен, поскольку он не удаляет ключи, значение которых в патче равно нулю. v.is_null() необходимо проверить перед рекурсивным вызовом merge - person Arnavion; 09.01.2019

Это сработало для меня

#[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);
}
person Harindaka    schedule 03.11.2017
comment
Похоже, если вы перешли на fn merge(a: Value, b: Value) -> Value, вы могли бы избежать клонирования. - person Shepmaster; 03.11.2017
comment
@Shepmaster Не могли бы вы добавить сюда ответ, включающий ваше предложение выше? Я новичок в Rust и еще не совсем понимаю, о чем вы говорите. Я приму ваш ответ, если вы это сделаете. - person Harindaka; 05.11.2017
comment
Что-то вроде этого, хотя мне немного грустно, что нужен unwrap. - person Shepmaster; 05.11.2017