You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@johnzon.apache.org by rm...@apache.org on 2021/05/12 09:29:00 UTC
[johnzon] branch master updated: [JOHNZON-344] try to support
serializers writing arrays directly into the enclosing object
This is an automated email from the ASF dual-hosted git repository.
rmannibucau pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/johnzon.git
The following commit(s) were added to refs/heads/master by this push:
new 6158e38 [JOHNZON-344] try to support serializers writing arrays directly into the enclosing object
6158e38 is described below
commit 6158e3822812c360c36449d153aa22862681b7b9
Author: Romain Manni-Bucau <rm...@gmail.com>
AuthorDate: Wed May 12 11:28:56 2021 +0200
[JOHNZON-344] try to support serializers writing arrays directly into the enclosing object
---
.../jsonb/api/experimental/JsonbExtensionTest.java | 215 ++++++++++++++++++++-
.../johnzon/mapper/DynamicMappingGenerator.java | 4 +-
2 files changed, 215 insertions(+), 4 deletions(-)
diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/api/experimental/JsonbExtensionTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/api/experimental/JsonbExtensionTest.java
index a8ade93..a29b4ba 100644
--- a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/api/experimental/JsonbExtensionTest.java
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/api/experimental/JsonbExtensionTest.java
@@ -29,16 +29,25 @@ import javax.json.JsonReader;
import javax.json.JsonValue;
import javax.json.JsonWriter;
import javax.json.JsonWriterFactory;
+import javax.json.bind.annotation.JsonbTypeDeserializer;
+import javax.json.bind.annotation.JsonbTypeSerializer;
+import javax.json.bind.serializer.DeserializationContext;
+import javax.json.bind.serializer.JsonbDeserializer;
+import javax.json.bind.serializer.JsonbSerializer;
+import javax.json.bind.serializer.SerializationContext;
import javax.json.stream.JsonGenerator;
+import javax.json.stream.JsonParser;
import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
+import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -99,7 +108,7 @@ public class JsonbExtensionTest {
}
@Test
- public void localTime() {
+ public void listInAdapter() {
final LocalDate date = LocalDate.of(2021, Month.valueOf("MAY"), 12);
final LocalTime time = LocalTime.of(1, 2, 0, 0);
final OffsetDateTime offsetDateTime = OffsetDateTime.of(date, time, ZoneOffset.UTC);
@@ -125,6 +134,33 @@ public class JsonbExtensionTest {
assertEquals(attribs, deserialized);
}
+ @Test
+ public void complexDeserializer() {
+ final LocalDate date = LocalDate.of(2021, Month.valueOf("MAY"), 12);
+ final LocalTime time = LocalTime.of(1, 2, 0, 0);
+ final OffsetDateTime offsetDateTime = OffsetDateTime.of(date, time, ZoneOffset.UTC);
+ final LocalDateTime localDateTime = LocalDateTime.of(date, time);
+
+ final Sink2 attribs = new Sink2();
+ attribs.attributes.put("ldateTime", new Wrapper2(singletonList(offsetDateTime)));
+ attribs.attributes.put("llocalDate", new Wrapper2(singletonList(date)));
+ attribs.attributes.put("llocalTime", new Wrapper2(singletonList(time)));
+ attribs.attributes.put("llocalDateTime", new Wrapper2(singletonList(localDateTime)));
+
+ final String json = jsonb.toJson(attribs);
+ assertJsonEquals("" +
+ "{\"attributes\":{" +
+ "\"ldateTime\":{\"value\":[\"2021-05-12T01:02Z\"]}" +
+ ",\"llocalDate\":{\"value\":[\"2021-05-12\"]}" +
+ ",\"llocalDateTime\":{\"value\":[\"2021-05-12T01:02\"]}" +
+ ",\"llocalTime\":{\"value\":[\"01:02\"]}" +
+ "}}",
+ json);
+
+ final Sink2 deserialized = jsonb.fromJson(json, Sink2.class);
+ assertEquals(attribs, deserialized);
+ }
+
// assumes json are valid but enables nicer diff
private void assertJsonEquals(final String expected, final String actual) {
final JsonWriterFactory writerFactory = Json.createWriterFactory(singletonMap(JsonGenerator.PRETTY_PRINTING, true));
@@ -168,6 +204,183 @@ public class JsonbExtensionTest {
}
}
+ @JsonbTypeSerializer(WrapperCodec.class)
+ @JsonbTypeDeserializer(WrapperCodec.class)
+ public static class Wrapper2 {
+ public Object value;
+
+ public Wrapper2() {
+ // no-op
+ }
+
+ public Wrapper2(final Object value) {
+ this.value = value;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(final Object obj) { // for test
+ return Wrapper2.class.isInstance(obj) &&
+ // Object so deserialization of times will be a string
+ String.valueOf(Wrapper2.class.cast(obj).value).equals(String.valueOf(value));
+ }
+
+ @Override
+ public int hashCode() { // for test
+ return super.hashCode();
+ }
+ }
+
+ public static class Sink2 implements Serializable {
+ private Map<String, Wrapper2> attributes = new TreeMap<>();
+
+ public Object get(final String name) {
+ final Wrapper2 att = attributes.get(name);
+ return att != null ? att.getValue() : null;
+ }
+
+ public void setAttributes(final Map<String, Wrapper2> attributes) {
+ this.attributes = attributes;
+ }
+
+ public Map<String, Wrapper2> getAttributes() {
+ return attributes;
+ }
+
+ @Override
+ public boolean equals(final Object obj) { // for test
+ return Sink2.class.isInstance(obj) && Objects.equals(Sink2.class.cast(obj).attributes, attributes);
+ }
+
+ @Override
+ public int hashCode() { // for test
+ return super.hashCode();
+ }
+ }
+
+ public static class WrapperCodec implements JsonbDeserializer<Wrapper2>, JsonbSerializer<Wrapper2> {
+ @Override
+ public void serialize(final Wrapper2 wrapper, final JsonGenerator generator, final SerializationContext ctx) {
+ final Object value = wrapper.getValue();
+ if (value == null) {
+ return;
+ }
+ if (value instanceof List) {
+ final List<Object> list = (List<Object>) value;
+ if (!list.isEmpty()) {
+ generator.writeStartObject();
+ writeArray(generator, list);
+ generator.writeEnd();
+ }
+ } else if (value instanceof String) {
+ generator.write(markerFor(value), (String) value);
+ } else {
+ throw new IllegalArgumentException(value.toString());
+ }
+ }
+
+ private void writeArray(final JsonGenerator generator, final List<Object> list) {
+ generator.writeStartArray("a" + markerFor(list.get(0)));
+ for (final Object o : list) {
+ generator.write(o.toString());
+ }
+ generator.writeEnd();
+ }
+
+ private String markerFor(final Object value) {
+ if (value instanceof String) {
+ return "s";
+ }
+ if (value instanceof OffsetDateTime) {
+ return "dt";
+ }
+ if (value instanceof LocalDate) {
+ return "ld";
+ }
+ if (value instanceof LocalTime) {
+ return "lt";
+ }
+ if (value instanceof LocalDateTime) {
+ return "ldt";
+ }
+ throw new IllegalArgumentException(value.toString());
+ }
+
+ @Override
+ public Wrapper2 deserialize(final JsonParser parser, final DeserializationContext ctx, final Type rtType) {
+ if (parser.next() == JsonParser.Event.START_OBJECT &&
+ parser.next() == JsonParser.Event.KEY_NAME) {
+ final String marker = parser.getString();
+ final Wrapper2 wrapper = new Wrapper2(getValue(parser, marker));
+ // close the object
+ parser.next();
+ if (wrapper != null) {
+ return wrapper;
+ }
+ }
+ throw new IllegalStateException("Not a valid wrapper");
+ }
+
+ private Object getValue(final JsonParser parser, final String marker) {
+ final JsonParser.Event valueType = parser.next();
+ if (valueType == JsonParser.Event.VALUE_NULL) {
+ return null;
+ }
+ if (valueType == JsonParser.Event.VALUE_STRING) {
+ final String strVal = parser.getString();
+ if ("s".equals(marker)) {
+ return strVal;
+ }
+ if ("dt".equals(marker)) {
+ return OffsetDateTime.parse(strVal);
+ }
+ if ("ld".equals(marker)) {
+ return LocalDate.parse(strVal);
+ }
+ if ("lt".equals(marker)) {
+ return LocalTime.parse(strVal);
+ }
+ if ("ldt".equals(marker)) {
+ return LocalDateTime.parse(strVal);
+ }
+ throw new IllegalStateException("Unknown type info: " + marker);
+ }
+ if (valueType == JsonParser.Event.VALUE_TRUE) {
+ return Boolean.TRUE;
+ }
+ if (valueType == JsonParser.Event.VALUE_FALSE) {
+ return Boolean.FALSE;
+ }
+ if (valueType == JsonParser.Event.START_ARRAY) {
+ return parseArrayListJson(marker, parser);
+ }
+ if (valueType == JsonParser.Event.END_ARRAY) {
+ return null;
+ }
+ throw new IllegalStateException("Unknown JSON valueType " + valueType);
+ }
+
+ private List<?> parseArrayListJson(final String arrayTypeInfo, final JsonParser parser) {
+ if (!arrayTypeInfo.startsWith("a")) {
+ throw new IllegalStateException("Unknown type: " + arrayTypeInfo);
+ }
+ final String typeInfo = arrayTypeInfo.substring(1);
+ final List<Object> content = new ArrayList<>();
+ do {
+ final Object val = getValue(parser, typeInfo);
+ if (val != null) {
+ content.add(val);
+ } else {
+ break;
+ }
+ } while (true);
+ return content;
+ }
+ }
+
public static class Sink implements Serializable {
private Map<String, Wrapper> attributes = new TreeMap<>();
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/DynamicMappingGenerator.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/DynamicMappingGenerator.java
index 820f9f9..ccf16c3 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/DynamicMappingGenerator.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/DynamicMappingGenerator.java
@@ -530,9 +530,7 @@ public class DynamicMappingGenerator implements MappingGenerator {
@Override
public JsonGenerator writeStartObject() {
level++;
- if (level > 0) {
- delegate.writeStartObject();
- }
+ delegate.writeStartObject();
return this;
}