diff --git a/blackbox-test/src/main/java/org/example/customer/node/HelloWithUnmapped.java b/blackbox-test/src/main/java/org/example/customer/node/HelloWithUnmapped.java new file mode 100644 index 00000000..a096b7b0 --- /dev/null +++ b/blackbox-test/src/main/java/org/example/customer/node/HelloWithUnmapped.java @@ -0,0 +1,12 @@ +package org.example.customer.node; + +import io.avaje.json.node.JsonObject; +import io.avaje.jsonb.Json; + +@Json +public record HelloWithUnmapped( + String name, + int count, + @Json.Unmapped + JsonObject other) { +} diff --git a/blackbox-test/src/test/java/org/example/customer/node/HelloWithUnmappedTest.java b/blackbox-test/src/test/java/org/example/customer/node/HelloWithUnmappedTest.java new file mode 100644 index 00000000..aecbd3f8 --- /dev/null +++ b/blackbox-test/src/test/java/org/example/customer/node/HelloWithUnmappedTest.java @@ -0,0 +1,25 @@ +package org.example.customer.node; + +import io.avaje.json.node.JsonObject; +import io.avaje.jsonb.JsonType; +import io.avaje.jsonb.Jsonb; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class HelloWithUnmappedTest { + + Jsonb jsonb = Jsonb.builder().build(); + JsonType jsonType = jsonb.type(HelloWithUnmapped.class); + + @Test + void test() { + var source = new HelloWithUnmapped("hi", 3, JsonObject.create().add("extra", "b").add("extra2", 54L)); + + String asJson = jsonType.toJson(source); + assertThat(asJson).isEqualTo("{\"name\":\"hi\",\"count\":3,\"extra\":\"b\",\"extra2\":54}"); + + HelloWithUnmapped fromJson = jsonType.fromJson(asJson); + assertThat(fromJson).isEqualTo(source); + } +} diff --git a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/ClassReader.java b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/ClassReader.java index 9a093ef7..d0818d74 100644 --- a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/ClassReader.java +++ b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/ClassReader.java @@ -462,7 +462,11 @@ private void writeFromJsonImplementation(Append writer, String varName) { writer.eol().append(" String type = null;").eol(); } if (unmappedField != null) { - writer.append(" Map unmapped = new LinkedHashMap<>();").eol(); + if (unmappedJsonNodeType()) { + writer.append(" var unmapped = io.avaje.json.node.JsonObject.create();").eol(); + } else { + writer.append(" var unmapped = new java.util.LinkedHashMap();").eol(); + } } writeFromJsonSwitch(writer, directLoad, varName); writer.eol(); @@ -480,6 +484,10 @@ private void writeFromJsonImplementation(Append writer, String varName) { writer.append(" }").eol(); } + private boolean unmappedJsonNodeType() { + return unmappedField.type().topType().startsWith("io.avaje.json.node."); + } + private void writeJsonBuildResult(Append writer, String varName) { writer.append(" // build and return %s", shortName).eol(); if (constructor == null) { @@ -618,8 +626,13 @@ private void writeFromJsonSwitch(Append writer, boolean defaultConstructor, Stri writer.append(" default:").eol(); final String unmappedFieldName = caseInsensitiveKeys ? "origFieldName" : "fieldName"; if (unmappedField != null) { - writer.append(" Object value = objectJsonAdapter.fromJson(reader);").eol(); - writer.append(" unmapped.put(%s, value);", unmappedFieldName).eol(); + if (unmappedJsonNodeType()) { + writer.append(" var value = jsonNodeAdapter.fromJson(reader);").eol(); + writer.append(" unmapped.add(%s, value);", unmappedFieldName).eol(); + } else { + writer.append(" var value = objectJsonAdapter.fromJson(reader);").eol(); + writer.append(" unmapped.put(%s, value);", unmappedFieldName).eol(); + } } else { writer.append(" reader.unmappedField(%s);", unmappedFieldName).eol(); writer.append(" reader.skipValue();").eol(); diff --git a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/FieldProperty.java b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/FieldProperty.java index a81586c8..bc6b4ed8 100644 --- a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/FieldProperty.java +++ b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/FieldProperty.java @@ -62,9 +62,15 @@ final class FieldProperty { adapterFieldName = "rawAdapter"; defaultValue = "null"; } else if (unmapped) { - genericType = GenericType.parse("java.lang.Object"); - adapterShortType = "JsonAdapter"; - adapterFieldName = "objectJsonAdapter"; + if (unmappedJsonObject()) { + genericType = GenericType.parse("io.avaje.json.node.JsonNode"); + adapterShortType = "JsonAdapter"; + adapterFieldName = "jsonNodeAdapter"; + } else { + genericType = GenericType.parse("java.lang.Object"); + adapterShortType = "JsonAdapter"; + adapterFieldName = "objectJsonAdapter"; + } defaultValue = "null"; } else { genericType = GenericType.parse(rawType); @@ -80,6 +86,10 @@ final class FieldProperty { } } + private boolean unmappedJsonObject() { + return rawType.startsWith("io.avaje.json.node"); + } + void setConstructorParam() { constructorParam = true; } @@ -180,9 +190,9 @@ private boolean nameHasIsPrefix() { } void addImports(Set importTypes) { - customSerializer.ifPresent(t -> importTypes.add(t.toString())); - if (unmapped) { - importTypes.add("java.util.*"); + customSerializer.ifPresent(importTypes::add); + if (unmapped && unmappedJsonObject()) { + importTypes.add("io.avaje.json.node.JsonNode"); } if (!raw) { genericType.addImports(importTypes); @@ -232,13 +242,19 @@ private String genericTypeReplacement(String asType, String replaceWith) { void writeToJson(Append writer, String varName, String prefix) { if (unmapped) { - writer.append("%sMap unmapped = ", prefix); + writer.append("%svar unmapped = ", prefix); writeGetValue(writer, varName, ";"); writer.eol(); writer.append("%sif (unmapped != null) {", prefix).eol(); - writer.append("%s for (Map.Entry entry : unmapped.entrySet()) {", prefix).eol(); - writer.append("%s writer.name(entry.getKey());", prefix).eol(); - writer.append("%s objectJsonAdapter.toJson(writer, entry.getValue());", prefix).eol(); + if (unmappedJsonObject()) { + writer.append("%s for (var entry : unmapped.elements().entrySet()) {", prefix).eol(); + writer.append("%s writer.name(entry.getKey());", prefix).eol(); + writer.append("%s jsonNodeAdapter.toJson(writer, entry.getValue());", prefix).eol(); + } else { + writer.append("%s for (var entry : unmapped.entrySet()) {", prefix).eol(); + writer.append("%s writer.name(entry.getKey());", prefix).eol(); + writer.append("%s objectJsonAdapter.toJson(writer, entry.getValue());", prefix).eol(); + } writer.append("%s }", prefix).eol(); writer.append("%s}", prefix).eol(); } else {