use super::Merge;
use crate::index::Path as IndexPath;
use crate::iter::Traverser;
use serde_json::{Map, Value};
use std::borrow::Borrow;
pub trait Union: Sized {
#[must_use]
fn union_all<T, V>(iter: impl IntoIterator<Item = V>) -> Self
where
T: Traverser,
V: Borrow<Value>;
#[must_use]
fn union_all_by<T, V, F>(iter: impl IntoIterator<Item = V>, union: &mut F) -> Self
where
T: Traverser,
V: Borrow<Value>,
F: FnMut(&IndexPath, &mut Value, Option<&Value>) -> bool;
fn union<T>(&mut self, other: &Self)
where
T: Traverser;
fn union_recursive<T>(&mut self, other: &Self)
where
T: Traverser;
#[inline]
#[must_use]
fn into_union<T>(mut self, other: &Self) -> Self
where
T: Traverser,
{
self.union::<T>(other);
self
}
#[inline]
#[must_use]
fn into_union_recursive<T>(mut self, other: &Self) -> Self
where
T: Traverser,
{
self.union_recursive::<T>(other);
self
}
}
impl Union for Value {
#[inline]
fn union_all<T, V>(values: impl IntoIterator<Item = V>) -> Self
where
T: Traverser,
V: Borrow<Value>,
{
Self::union_all_by::<T, V, _>(values, &mut union_func)
}
#[inline]
fn union_all_by<T, V, F>(values: impl IntoIterator<Item = V>, union: &mut F) -> Self
where
T: Traverser,
V: Borrow<Value>,
F: FnMut(&IndexPath, &mut Value, Option<&Value>) -> bool,
{
let mut result = Value::Object(Map::default());
for v in values {
result.merge_by_recursive::<T, F>(v.borrow(), union);
}
result
}
#[inline]
fn union<T>(&mut self, other: &Self)
where
T: Traverser,
{
self.merge_by::<T, _>(other, &mut union_func);
}
#[inline]
fn union_recursive<T>(&mut self, other: &Self)
where
T: Traverser,
{
self.merge_by_recursive::<T, _>(other, &mut union_func);
}
}
fn union_func(_idx: &IndexPath, this: &mut Value, other: Option<&Value>) -> bool {
match (this, other) {
(&mut Value::Object(ref mut res), Some(Value::Object(other))) => {
for k in other.keys() {
res.entry(k.clone()).or_insert(Value::Null);
}
true
}
(&mut Value::Array(ref mut this), Some(Value::Array(other))) => {
this.extend(other.clone());
false
}
(_, Some(&Value::Null)) => false,
(this @ &mut Value::Null, Some(other)) => {
*this = other.clone();
false
}
_ => false,
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::iter::dfs::Dfs;
use itertools::Itertools;
use pretty_assertions::assert_eq;
use serde_json::{Value, json};
#[test]
fn union_all_custom_func() {
let items = [
json!({
"title": "This is a title",
"person" : {
"firstName" : "John",
"lastName" : "Doe"
},
"cities": [ "london", "paris" ]
}),
json!({
"person" : {
"firstName" : "John",
},
}),
json!({
"another" : 1,
}),
json!({
"another" : {
"another" : 2,
},
}),
json!({
"person" : {
"firstName" : {
"test": "John",
},
"gender" : {
"important": false,
},
},
}),
];
let expected = json!({
"title": "This is a title",
"person" : {
"lastName" : "Doe",
"firstName" : {
"test": "John",
},
"gender" : {
"important": false,
},
},
"cities": [ "london", "paris" ],
"another" : {
"another" : 2,
},
});
let mut custom_union_func =
|_idx: &IndexPath, this: &mut Value, other: Option<&Value>| -> bool {
#[expect(
clippy::match_same_arms,
reason = "separate arms document distinct merge policies"
)]
match (this, other) {
(&mut Value::Object(ref mut this), Some(Value::Object(other))) => {
for k in other.keys() {
this.entry(k.clone()).or_insert(Value::Null);
}
true
}
(Value::Object(_), Some(_)) => false,
(this, Some(other @ &Value::Object(_))) => {
*this = other.clone();
false
}
(_, Some(&Value::Null)) => false,
(this @ &mut Value::Null, Some(other)) => {
*this = other.clone();
false
}
(this, Some(other)) => {
*this = other.clone();
false
}
_ => false,
}
};
let len = items.len();
for perm in items.into_iter().permutations(len) {
let union: Value = Union::union_all_by::<Dfs, _, _>(&perm, &mut custom_union_func);
assert_eq!(&union, &expected);
}
}
#[test]
fn union_recursive_complex() {
let base = json!({
"title": "This is a title",
"person" : {
"firstName" : "John",
"lastName" : "Doe"
},
"cities": [ "london", "paris" ]
});
let merge = json!({
"title": "",
"person" : {
"firstName" : "",
"lastName" : "",
"new" : "this field is new"
},
"cities": [ "london" ],
"new": [ "this is new" ],
});
let expected = json!({
"title": "This is a title",
"person" : {
"firstName" : "John",
"lastName" : "Doe",
"new" : "this field is new"
},
"cities": [ "london", "paris", "london" ],
"new": [ "this is new" ],
});
assert_eq!(&base.into_union_recursive::<Dfs>(&merge), &expected);
}
}