diff --git a/json-node/src/main/java/io/avaje/json/node/JsonArray.java b/json-node/src/main/java/io/avaje/json/node/JsonArray.java index a860ed7f..570d9ab5 100644 --- a/json-node/src/main/java/io/avaje/json/node/JsonArray.java +++ b/json-node/src/main/java/io/avaje/json/node/JsonArray.java @@ -37,6 +37,24 @@ public String toString() { return text(); } + @Override + public JsonArray unmodifiable() { + final var newList = new ArrayList(children.size()); + for (JsonNode child : children) { + newList.add(child.unmodifiable()); + } + return of(newList); + } + + @Override + public JsonArray copy() { + final var newList = new ArrayList(children.size()); + for (JsonNode child : children) { + newList.add(child.copy()); + } + return new JsonArray(newList); + } + @Override public Type type() { return Type.ARRAY; diff --git a/json-node/src/main/java/io/avaje/json/node/JsonBoolean.java b/json-node/src/main/java/io/avaje/json/node/JsonBoolean.java index cc7bce5c..686ab00e 100644 --- a/json-node/src/main/java/io/avaje/json/node/JsonBoolean.java +++ b/json-node/src/main/java/io/avaje/json/node/JsonBoolean.java @@ -1,6 +1,6 @@ package io.avaje.json.node; -public final class JsonBoolean implements JsonNode { +public final /*value*/ class JsonBoolean implements JsonNode { private final boolean value; @@ -17,6 +17,16 @@ public String toString() { return text(); } + @Override + public JsonBoolean unmodifiable() { + return this; + } + + @Override + public JsonBoolean copy() { + return this; + } + @Override public Type type() { return Type.BOOLEAN; diff --git a/json-node/src/main/java/io/avaje/json/node/JsonDecimal.java b/json-node/src/main/java/io/avaje/json/node/JsonDecimal.java index 587aeae4..4409584d 100644 --- a/json-node/src/main/java/io/avaje/json/node/JsonDecimal.java +++ b/json-node/src/main/java/io/avaje/json/node/JsonDecimal.java @@ -4,7 +4,7 @@ import java.math.BigDecimal; -public final class JsonDecimal implements JsonNumber { +public final /*value*/ class JsonDecimal implements JsonNumber { private final BigDecimal value; @@ -21,6 +21,16 @@ public String toString() { return text(); } + @Override + public JsonDecimal unmodifiable() { + return this; + } + + @Override + public JsonDecimal copy() { + return this; + } + @Override public Type type() { return Type.NUMBER; diff --git a/json-node/src/main/java/io/avaje/json/node/JsonDouble.java b/json-node/src/main/java/io/avaje/json/node/JsonDouble.java index 1760e92b..6903b84a 100644 --- a/json-node/src/main/java/io/avaje/json/node/JsonDouble.java +++ b/json-node/src/main/java/io/avaje/json/node/JsonDouble.java @@ -4,7 +4,7 @@ import java.math.BigDecimal; -public final class JsonDouble implements JsonNumber { +public final /*value*/ class JsonDouble implements JsonNumber { private final double value; @@ -21,6 +21,16 @@ public String toString() { return text(); } + @Override + public JsonDouble unmodifiable() { + return this; + } + + @Override + public JsonDouble copy() { + return this; + } + @Override public Type type() { return Type.NUMBER; diff --git a/json-node/src/main/java/io/avaje/json/node/JsonInteger.java b/json-node/src/main/java/io/avaje/json/node/JsonInteger.java index 9f8994bf..c7011c22 100644 --- a/json-node/src/main/java/io/avaje/json/node/JsonInteger.java +++ b/json-node/src/main/java/io/avaje/json/node/JsonInteger.java @@ -4,7 +4,7 @@ import java.math.BigDecimal; -public final class JsonInteger implements JsonNumber { +public final /*value*/ class JsonInteger implements JsonNumber { private final int value; @@ -21,6 +21,16 @@ public String toString() { return text(); } + @Override + public JsonInteger unmodifiable() { + return this; + } + + @Override + public JsonInteger copy() { + return this; + } + @Override public Type type() { return Type.NUMBER; diff --git a/json-node/src/main/java/io/avaje/json/node/JsonLong.java b/json-node/src/main/java/io/avaje/json/node/JsonLong.java index c5bbd60e..8cd4c7a4 100644 --- a/json-node/src/main/java/io/avaje/json/node/JsonLong.java +++ b/json-node/src/main/java/io/avaje/json/node/JsonLong.java @@ -4,7 +4,7 @@ import java.math.BigDecimal; -public final class JsonLong implements JsonNumber { +public final /*value*/ class JsonLong implements JsonNumber { private final long value; @@ -21,6 +21,16 @@ public String toString() { return text(); } + @Override + public JsonLong unmodifiable() { + return this; + } + + @Override + public JsonLong copy() { + return this; + } + @Override public Type type() { return Type.NUMBER; diff --git a/json-node/src/main/java/io/avaje/json/node/JsonNode.java b/json-node/src/main/java/io/avaje/json/node/JsonNode.java index adcefb72..97aabe50 100644 --- a/json-node/src/main/java/io/avaje/json/node/JsonNode.java +++ b/json-node/src/main/java/io/avaje/json/node/JsonNode.java @@ -72,6 +72,16 @@ public boolean isObject() { */ String text(); + /** + * Return an unmodifiable deep copy of the JsonNode. + */ + JsonNode unmodifiable(); + + /** + * Return a mutable deep copy of the JsonNode. + */ + JsonNode copy(); + /** * Find a node given a path using dot notation. * @param path The path in dot notation diff --git a/json-node/src/main/java/io/avaje/json/node/JsonObject.java b/json-node/src/main/java/io/avaje/json/node/JsonObject.java index 03b5b095..d3939d9a 100644 --- a/json-node/src/main/java/io/avaje/json/node/JsonObject.java +++ b/json-node/src/main/java/io/avaje/json/node/JsonObject.java @@ -51,6 +51,24 @@ public String text() { return children.toString(); } + @Override + public JsonObject unmodifiable() { + final var mapCopy = new LinkedHashMap(); + for (Map.Entry entry : children.entrySet()) { + mapCopy.put(entry.getKey(), entry.getValue().unmodifiable()); + } + return JsonObject.of(mapCopy); + } + + @Override + public JsonObject copy() { + final var mapCopy = new LinkedHashMap(); + for (Map.Entry entry : children.entrySet()) { + mapCopy.put(entry.getKey(), entry.getValue().copy()); + } + return new JsonObject(mapCopy); + } + /** * Return true if the json object contains no elements. */ diff --git a/json-node/src/main/java/io/avaje/json/node/JsonString.java b/json-node/src/main/java/io/avaje/json/node/JsonString.java index 5cb4a88c..36e1c505 100644 --- a/json-node/src/main/java/io/avaje/json/node/JsonString.java +++ b/json-node/src/main/java/io/avaje/json/node/JsonString.java @@ -1,6 +1,6 @@ package io.avaje.json.node; -public final class JsonString implements JsonNode { +public final /*value*/ class JsonString implements JsonNode { private final String value; @@ -17,6 +17,16 @@ public String toString() { return text(); } + @Override + public JsonString unmodifiable() { + return this; + } + + @Override + public JsonString copy() { + return this; + } + @Override public Type type() { return Type.STRING; diff --git a/json-node/src/test/java/io/avaje/json/node/JsonArrayTest.java b/json-node/src/test/java/io/avaje/json/node/JsonArrayTest.java index c1a4a58c..64a7087a 100644 --- a/json-node/src/test/java/io/avaje/json/node/JsonArrayTest.java +++ b/json-node/src/test/java/io/avaje/json/node/JsonArrayTest.java @@ -6,6 +6,7 @@ import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; class JsonArrayTest { @@ -87,4 +88,31 @@ void add() { assertThat(elements.get(4)).isInstanceOf(JsonObject.class); } + @Test + void copy() { + final JsonArray source = JsonArray.create() + .add("foo") + .add(JsonObject.create().add("b", 42)); + + JsonArray copy = source.copy(); + assertThat(copy.toString()).isEqualTo(source.toString()); + + copy.add("canMutate"); + assertThat(copy.size()).isEqualTo(3); + assertThat(source.size()).isEqualTo(2); + } + + @Test + void unmodifiable() { + final JsonArray source = JsonArray.create() + .add("foo") + .add(JsonObject.create().add("b", 42)); + + JsonArray copy = source.unmodifiable(); + assertThat(copy.toString()).isEqualTo(source.toString()); + + assertThatThrownBy(() -> copy.add("canMutate")) + .isInstanceOf(UnsupportedOperationException.class); + } + } diff --git a/json-node/src/test/java/io/avaje/json/node/JsonObjectTest.java b/json-node/src/test/java/io/avaje/json/node/JsonObjectTest.java index 34bcb94d..07d10294 100644 --- a/json-node/src/test/java/io/avaje/json/node/JsonObjectTest.java +++ b/json-node/src/test/java/io/avaje/json/node/JsonObjectTest.java @@ -5,9 +5,9 @@ import java.math.BigDecimal; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; class JsonObjectTest { @@ -116,4 +116,31 @@ void findNested() { assertThat(node.extract("address.other.deep")).isEqualTo("one"); } + + @Test + void copy() { + final JsonObject source = JsonObject.create() + .add("name", "foo") + .add("other", JsonObject.create().add("b", 42)); + + JsonObject copy = source.copy(); + assertThat(copy.toString()).isEqualTo(source.toString()); + + copy.add("canMutate", true); + assertThat(copy.containsKey("canMutate")).isTrue(); + assertThat(source.containsKey("canMutate")).isFalse(); + } + + @Test + void unmodifiable() { + final JsonObject source = JsonObject.create() + .add("name", "foo") + .add("other", JsonObject.create().add("b", 42)); + + JsonObject copy = source.unmodifiable(); + assertThat(copy.toString()).isEqualTo(source.toString()); + + assertThatThrownBy(() -> copy.add("canMutate", true)) + .isInstanceOf(UnsupportedOperationException.class); + } }