///|
/// A deep copy, so merges never alias the maps of their inputs.
fn clone(value : Json) -> Json {
match value {
Object(fields) => {
let copy : Map[String, Json] = Map([])
for key, item in fields {
copy[key] = clone(item)
}
Json::object(copy)
}
Array(items) => Json::array(items.map(clone))
other => other
}
}
///|
/// Merge `src` into `dst` and return the result, following mermaid's
/// `assignWithDepth`: objects merge recursively down to `depth` levels and
/// are shallowly assigned below that; arrays gain the elements they lack;
/// an array merged into an object merges each of its elements in turn.
/// `Json::null()` plays the role of both `undefined` and `null`.
pub fn assign_with_depth(dst : Json, src : Json, depth~ : Int32 = 2) -> Json {
match (dst, src) {
(Array(target), Array(items)) => {
let merged = target.copy()
for item in items {
if !merged.contains(item) {
merged.push(clone(item))
}
}
return Json::array(merged)
}
(_, Array(items)) => {
let mut merged = dst
for item in items {
merged = assign_with_depth(merged, item, depth~)
}
return merged
}
_ => ()
}
if dst is Null || depth <= 0 {
return match (dst, src) {
(Object(target), Object(fields)) => {
let merged = target.copy()
for key, value in fields {
merged[key] = clone(value)
}
Json::object(merged)
}
(Object(_) | Array(_), Null) => dst
_ => clone(src)
}
}
guard (dst, src) is (Object(target), Object(fields)) else { return dst }
let merged = target.copy()
for key, value in fields {
let current = merged.get(key).unwrap_or(Json::null())
match value {
Null => ()
Object(_) | Array(_) => {
let current = match (current, value) {
(Null, Array(_)) => Json::array([])
(Null, _) => Json::object({})
_ => current
}
if current is (Object(_) | Array(_)) {
merged[key] = assign_with_depth(current, value, depth=depth - 1)
}
}
_ => if !(current is (Object(_) | Array(_))) { merged[key] = value }
}
}
Json::object(merged)
}