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 <rmannibu...@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; }