diff --git a/src/net/sourceforge/plantuml/json/JsonObject.java b/src/net/sourceforge/plantuml/json/JsonObject.java index f03059a9c..c2b9581a5 100644 --- a/src/net/sourceforge/plantuml/json/JsonObject.java +++ b/src/net/sourceforge/plantuml/json/JsonObject.java @@ -555,6 +555,33 @@ public class JsonObject extends JsonValue implements Iterable { return this; } + /** + * Copies all members of the specified object into this object. When the specified object contains + * members with names that also exist in this object, the existing values in this object will be + * replaced by the corresponding values in the specified object, except for the case that both values + * are JsonObjects themselves, which will trigger another merge of these objects. + * + * @param object the object to deep merge + * @return the object itself, to enable method chaining + */ + public JsonObject deepMerge(JsonObject object) { + if (object == null) { + throw new NullPointerException("object is null"); + } + for (Member member : object) { + final String name = member.name; + JsonValue value = member.value; + if (value instanceof JsonObject) { + final JsonValue existingValue = this.get(member.name); + if (existingValue instanceof JsonObject) { + value = ((JsonObject) existingValue).deepMerge((JsonObject) value); + } + } + this.set(name, value); + } + return this; + } + /** * Returns the value of the member with the specified name in this object. If * this object contains multiple members with the given name, this method will diff --git a/test/net/sourceforge/plantuml/json/JsonObjectTest.java b/test/net/sourceforge/plantuml/json/JsonObjectTest.java new file mode 100644 index 000000000..9666a0b14 --- /dev/null +++ b/test/net/sourceforge/plantuml/json/JsonObjectTest.java @@ -0,0 +1,89 @@ +package net.sourceforge.plantuml.json; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNullPointerException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.IndicativeSentencesGeneration; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@IndicativeSentencesGeneration(separator = ": ", generator = ReplaceUnderscores.class) +class JsonObjectTest { + + private JsonObject object; + + @BeforeEach + void setUp() { + object = new JsonObject(); + } + + @Nested + class Merge_Test { + @Test + void merge_fails_With_Null() { + assertThatNullPointerException().isThrownBy(() -> object.merge(null)).withMessage("object is null"); + } + + @Test + void merge_appends_Members() { + object.add("a", 1).add("b", 1); + object.merge(Json.object().add("c", 2).add("d", 2)); + + assertThat(object).isEqualTo(Json.object().add("a", 1).add("b", 1).add("c", 2).add("d", 2)); + } + + @Test + void merge_replaces_Members() { + object.add("a", 1).add("b", 1).add("c", 1); + object.merge(Json.object().add("b", 2).add("d", 2)); + + assertThat(object).isEqualTo(Json.object().add("a", 1).add("b", 2).add("c", 1).add("d", 2)); + } + + @Test + void merge_replaces_Members_With_no_deep() { + object.add("a", 1).add("b", Json.object().add("x", 1).add("y", 1)).add("c", Json.object().add("A", 1)); + object.merge(Json.object().add("b", Json.object().add("y", 2).add("z", 1)).add("c", 1) + .add("d", Json.object().add("B", 1))); + + assertThat(object).isEqualTo(Json.object().add("a", 1).add("b", Json.object().add("y", 2).add("z", 1)).add("c", 1) + .add("d", Json.object().add("B", 1))); + } + } + + @Nested + class DeepMerge_Test { + @Test + void deepMerge_fails_With_Null() { + assertThatNullPointerException().isThrownBy(() -> object.deepMerge(null)).withMessage("object is null"); + } + + @Test + void deepMerge_appends_Members() { + object.add("a", 1).add("b", 1); + object.deepMerge(Json.object().add("c", 2).add("d", 2)); + + assertThat(object).isEqualTo(Json.object().add("a", 1).add("b", 1).add("c", 2).add("d", 2)); + } + + @Test + void deepMerge_replaces_Members() { + object.add("a", 1).add("b", 1).add("c", 1); + object.deepMerge(Json.object().add("b", 2).add("d", 2)); + + assertThat(object).isEqualTo(Json.object().add("a", 1).add("b", 2).add("c", 1).add("d", 2)); + } + + @Test + void deepMerge_merges_Member_Object() { + object.add("a", 1).add("b", Json.object().add("x", 1).add("y", 1)).add("c", Json.object().add("A", 1)); + object.deepMerge(Json.object().add("b", Json.object().add("y", 2).add("z", 1)).add("c", 1) + .add("d", Json.object().add("B", 1))); + + assertThat(object).isEqualTo(Json.object().add("a", 1).add("b", Json.object().add("x", 1).add("y", 2).add("z", 1)).add("c", 1) + .add("d", Json.object().add("B", 1))); + } + } +}