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 c955174 [JOHNZON-358][JOHNZON-357] enhance (de)serializer support on list/map types c955174 is described below commit c95517479aab2c3934e54f1cdbf5fc9b449925e5 Author: Romain Manni-Bucau <rmannibu...@gmail.com> AuthorDate: Tue Jan 25 17:17:59 2022 +0100 [JOHNZON-358][JOHNZON-357] enhance (de)serializer support on list/map types --- .../org/apache/johnzon/core/JsonGeneratorImpl.java | 17 ++-- johnzon-jsonb/pom.xml | 2 +- .../apache/johnzon/jsonb/SerializersMapTest.java | 106 +++++++++++++++++++++ .../SerializersObjectWithEmbeddedListTest.java | 87 +++++++++++++++++ .../org/apache/johnzon/jsonb/test/JsonbRule.java | 24 +++-- .../johnzon/mapper/DynamicMappingGenerator.java | 1 + .../johnzon/mapper/MappingGeneratorImpl.java | 66 +++++++------ 7 files changed, 252 insertions(+), 51 deletions(-) diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonGeneratorImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonGeneratorImpl.java index 7520243..f15078b 100644 --- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonGeneratorImpl.java +++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonGeneratorImpl.java @@ -34,11 +34,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Iterator; import java.util.Map; class JsonGeneratorImpl implements JsonGenerator, JsonChars, Serializable { - private static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); + private static final Charset UTF8_CHARSET = StandardCharsets.UTF_8; private final transient Writer writer; private final BufferStrategy.BufferProvider<char[]> bufferProvider; @@ -184,20 +185,17 @@ class JsonGeneratorImpl implements JsonGenerator, JsonChars, Serializable { switch (value.getValueType()) { case ARRAY: writeStartArray(name); - final JsonArray array = JsonArray.class.cast(value); - final Iterator<JsonValue> ait = array.iterator(); - while (ait.hasNext()) { - write(ait.next()); + final JsonArray array = value.asJsonArray(); + for (final JsonValue jsonValue : array) { + write(jsonValue); } writeEnd(); break; case OBJECT: writeStartObject(name); - final JsonObject object = JsonObject.class.cast(value); - final Iterator<Map.Entry<String, JsonValue>> oit = object.entrySet().iterator(); - while (oit.hasNext()) { - final Map.Entry<String, JsonValue> keyval = oit.next(); + final JsonObject object = value.asJsonObject(); + for (final Map.Entry<String, JsonValue> keyval : object.entrySet()) { write(keyval.getKey(), keyval.getValue()); } writeEnd(); @@ -828,5 +826,4 @@ class JsonGeneratorImpl implements JsonGenerator, JsonChars, Serializable { } } */ - } diff --git a/johnzon-jsonb/pom.xml b/johnzon-jsonb/pom.xml index dcbd370..82c367e 100644 --- a/johnzon-jsonb/pom.xml +++ b/johnzon-jsonb/pom.xml @@ -37,7 +37,7 @@ <dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-annotation_1.3_spec</artifactId> - <version>1.1</version> + <version>1.3</version> <scope>provided</scope> <optional>true</optional> </dependency> diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/SerializersMapTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/SerializersMapTest.java new file mode 100644 index 0000000..4e14592 --- /dev/null +++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/SerializersMapTest.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.johnzon.jsonb; + +import org.apache.johnzon.jsonb.test.JsonbRule; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +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.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class SerializersMapTest { + @Rule + public final JsonbRule jsonb = new JsonbRule().withFormatting(true); + + @Before + public void init() { + MapDeSer.serializerCalled = false; + MapDeSer.deserializerCalled = false; + } + + @Test + public void serializeMapTest() { + MapModel mapModel = new MapModel(); + mapModel.map.put("key1", "value1"); + mapModel.map.put("key2", "value2"); + + assertEquals("" + + "{\n" + + " \"map\":{\n" + + " \"key1\":\"value1\",\n" + + " \"key2\":\"value2\"\n" + + " }\n" + + "}" + + "", jsonb.toJson(mapModel)); + + assertTrue(MapDeSer.serializerCalled); + assertFalse(MapDeSer.deserializerCalled); + } + + @Test + public void deserializeMapTest() { + final Map<String, String> expected = new HashMap<>(); + expected.put("key1", "value1"); + expected.put("key2", "value2"); + + assertEquals(expected, jsonb.fromJson("{\"map\":{\"key1\":\"value1\",\"key2\":\"value2\"}}", MapModel.class).map); + + assertFalse(MapDeSer.serializerCalled); + assertTrue(MapDeSer.deserializerCalled); + } + + public static class MapModel implements Serializable { + @JsonbTypeSerializer(MapDeSer.class) + @JsonbTypeDeserializer(MapDeSer.class) + public Map<String, String> map = new HashMap<>(); + } + + public static class MapDeSer<T> implements JsonbSerializer<T>, JsonbDeserializer<T> { + private static boolean serializerCalled; + private static boolean deserializerCalled; + + @Override + public T deserialize(final JsonParser parser, final DeserializationContext ctx, final Type rtType) { + deserializerCalled = true; + return ctx.deserialize(rtType, parser); + } + + @Override + public void serialize(final T obj, final JsonGenerator generator, final SerializationContext ctx) { + serializerCalled = true; + ctx.serialize(obj, generator); + } + } +} diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/SerializersObjectWithEmbeddedListTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/SerializersObjectWithEmbeddedListTest.java new file mode 100644 index 0000000..81b8293 --- /dev/null +++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/SerializersObjectWithEmbeddedListTest.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.johnzon.jsonb; + +import org.apache.johnzon.jsonb.test.JsonbRule; +import org.junit.Rule; +import org.junit.Test; + +import javax.json.bind.annotation.JsonbTypeSerializer; +import javax.json.bind.serializer.JsonbSerializer; +import javax.json.bind.serializer.SerializationContext; +import javax.json.stream.JsonGenerator; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class SerializersObjectWithEmbeddedListTest { + @Rule + public final JsonbRule jsonb = new JsonbRule().withFormatting(true); + + @Test + public void serializeTest() throws Exception { + ObjectModel objectModel = new ObjectModel(); + objectModel.embeddedList.add("Text1"); + objectModel.embeddedList.add("Text2"); + objectModel.otherField = "Other Text"; + + WrapperModel wrapper = new WrapperModel(); + wrapper.object = objectModel; + + assertEquals("" + + "{\n" + + " \"object\":{\n" + + " \"embeddedList\":[\n" + + " \"Text1\",\n" + + " \"Text2\"\n" + + " ],\n" + + " \"otherField\":\"Other Text\",\n" + + " \"otherField2\":\"Other Text\",\n" + + " \"embeddedList2\":[\n" + + " \"Text1\",\n" + + " \"Text2\"\n" + + " ],\n" + + " \"otherField3\":\"Other Text\"\n" + + " }\n" + + "}" + + "", jsonb.toJson(wrapper)); + } + + public static class WrapperModel { + public ObjectModel object; + } + + @JsonbTypeSerializer(ObjectDeSer.class) + public static class ObjectModel { + public List<String> embeddedList = new ArrayList<>(); + public String otherField; + } + + public static class ObjectDeSer implements JsonbSerializer<ObjectModel> { + @Override + public void serialize(final ObjectModel obj, final JsonGenerator generator, final SerializationContext ctx) { + ctx.serialize("embeddedList", obj.embeddedList, generator); + ctx.serialize("otherField", obj.otherField, generator); + ctx.serialize("otherField2", obj.otherField, generator); + ctx.serialize("embeddedList2", obj.embeddedList, generator); + ctx.serialize("otherField3", obj.otherField, generator); + } + } +} diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/test/JsonbRule.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/test/JsonbRule.java index b62d689..2bb0a58 100644 --- a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/test/JsonbRule.java +++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/test/JsonbRule.java @@ -18,11 +18,10 @@ */ package org.apache.johnzon.jsonb.test; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Reader; -import java.io.Writer; -import java.lang.reflect.Type; +import org.apache.johnzon.jsonb.api.experimental.JsonbExtension; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; import javax.json.JsonValue; import javax.json.bind.Jsonb; @@ -31,11 +30,11 @@ import javax.json.bind.JsonbConfig; import javax.json.bind.JsonbException; import javax.json.stream.JsonGenerator; import javax.json.stream.JsonParser; - -import org.apache.johnzon.jsonb.api.experimental.JsonbExtension; -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.lang.reflect.Type; public class JsonbRule implements TestRule, Jsonb, JsonbExtension { private Jsonb jsonb; @@ -47,6 +46,11 @@ public class JsonbRule implements TestRule, Jsonb, JsonbExtension { return this; } + public JsonbRule withFormatting(final boolean format) { + config.withFormatting(format); + return this; + } + @Override public Statement apply(final Statement statement, final Description description) { return new Statement() { 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 f5cb2ea..163eade 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 @@ -362,6 +362,7 @@ public class DynamicMappingGenerator implements MappingGenerator { if (nested == 0) { final JsonGenerator unwrap = unwrap(delegate); unwrap.writeEnd(); + implicitStart = false; } else { delegate.writeEnd(); } diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java index bdfba2a..224bdd4 100644 --- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java +++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java @@ -86,10 +86,8 @@ public class MappingGeneratorImpl implements MappingGenerator { } else { final ObjectConverter.Writer objectConverter = config.findObjectConverterWriter(objectClass); if (objectConverter != null) { - final DynamicMappingGenerator dynamicMappingGenerator = new DynamicMappingGenerator(this, - generator::writeStartObject, generator::writeEnd, null); - objectConverter.writeJson(object, dynamicMappingGenerator); - dynamicMappingGenerator.flushIfNeeded(); + writeWithObjectConverter(new DynamicMappingGenerator(this, + generator::writeStartObject, generator::writeEnd, null), objectConverter, object); } else { writeValue(objectClass, false, false, false, false, false, null, key, object, null, emptyList(), isDedup() ? JsonPointerTracker.ROOT : null, generator); @@ -176,10 +174,8 @@ public class MappingGeneratorImpl implements MappingGenerator { if (!writeBody) { objectConverter.writeJson(object, this); } else { - final DynamicMappingGenerator dynamicMappingGenerator = new DynamicMappingGenerator(this, - generator::writeStartObject, generator::writeEnd, null); - objectConverter.writeJson(object, dynamicMappingGenerator); - dynamicMappingGenerator.flushIfNeeded(); + writeWithObjectConverter(new DynamicMappingGenerator(this, + generator::writeStartObject, generator::writeEnd, null), objectConverter, object); } } else { if (classMapping == null) { // will be created anyway now so force it and if it has an adapter respect it @@ -369,9 +365,7 @@ public class MappingGeneratorImpl implements MappingGenerator { } if (classMapping.writer != null) { - final DynamicMappingGenerator gen = new DynamicMappingGenerator.SkipEnclosingWriteEnd(this, null, generator); - classMapping.writer.writeJson(object, gen); - gen.flushIfNeeded(); + writeWithObjectConverter(new DynamicMappingGenerator.SkipEnclosingWriteEnd(this, null, generator), classMapping.writer, object); return false; } if (classMapping.adapter != null) { @@ -460,14 +454,17 @@ public class MappingGeneratorImpl implements MappingGenerator { Iterable.class.cast(value).iterator(), value); } else if ((!dynamic && map) || (dynamic && Map.class.isAssignableFrom(type))) { generator.writeStartObject(key); - writeMapBody((Map<?, ?>) value, itemConverter); + if (objectConverter != null) { + writeWithObjectConverter(new DynamicMappingGenerator(this, + () -> this.generator.writeStartObject(key), this.generator::writeEnd, key), objectConverter, value); + } else { + writeMapBody((Map<?, ?>) value, itemConverter); + } generator.writeEnd(); } else if ((!dynamic && primitive) || (dynamic && Mappings.isPrimitive(type))) { if (objectConverter != null) { - final DynamicMappingGenerator dynamicMappingGenerator = new DynamicMappingGenerator(this, - () -> this.generator.writeStartObject(key), this.generator::writeEnd, key); - objectConverter.writeJson(value, dynamicMappingGenerator); - dynamicMappingGenerator.flushIfNeeded(); + writeWithObjectConverter(new DynamicMappingGenerator(this, + () -> this.generator.writeStartObject(key), this.generator::writeEnd, key), objectConverter, value); } else { writePrimitives(key, type, value, generator); } @@ -475,14 +472,19 @@ public class MappingGeneratorImpl implements MappingGenerator { writeIterator(itemConverter, key, objectConverter, ignoredProperties, jsonPointer, generator, BaseStream.class.cast(value).iterator(), value); } else if (Iterator.class.isAssignableFrom(type)) { - writeIterator(itemConverter, key, objectConverter, ignoredProperties, jsonPointer, generator, - Iterator.class.cast(value), value); + if (objectConverter != null) { + generator.writeStartObject(key); + writeWithObjectConverter(new DynamicMappingGenerator(this, + () -> this.generator.writeStartObject(key), this.generator::writeEnd, key), objectConverter, value); + generator.writeEnd(); + } else { + writeIterator(itemConverter, key, objectConverter, ignoredProperties, jsonPointer, generator, + Iterator.class.cast(value), value); + } } else { if (objectConverter != null) { - final DynamicMappingGenerator dynamicMappingGenerator = new DynamicMappingGenerator(this, - () -> this.generator.writeStartObject(key), this.generator::writeEnd, key); - objectConverter.writeJson(value, dynamicMappingGenerator); - dynamicMappingGenerator.flushIfNeeded(); + writeWithObjectConverter(new DynamicMappingGenerator(this, + () -> this.generator.writeStartObject(key), this.generator::writeEnd, key), objectConverter, value); return; } @@ -501,10 +503,8 @@ public class MappingGeneratorImpl implements MappingGenerator { } if (objectConverterToUse != null) { - final DynamicMappingGenerator dynamicMappingGenerator = new DynamicMappingGenerator(this, - () -> this.generator.writeStartObject(key), this.generator::writeEnd, key); - objectConverterToUse.writeJson(value, dynamicMappingGenerator); - dynamicMappingGenerator.flushIfNeeded(); + writeWithObjectConverter(new DynamicMappingGenerator(this, + () -> this.generator.writeStartObject(key), this.generator::writeEnd, key), objectConverterToUse, value); return; } } @@ -518,6 +518,14 @@ public class MappingGeneratorImpl implements MappingGenerator { } } + private void writeWithObjectConverter(final DynamicMappingGenerator generator, + final ObjectConverter.Writer objectConverter, + final Object value) { + final DynamicMappingGenerator dynamicMappingGenerator = generator; + objectConverter.writeJson(value, dynamicMappingGenerator); + dynamicMappingGenerator.flushIfNeeded(); + } + private void writeIterator(final Adapter itemConverter, final String key, final ObjectConverter.Writer objectConverter, final Collection<String> ignoredProperties, @@ -550,10 +558,8 @@ public class MappingGeneratorImpl implements MappingGenerator { } if (objectConverterToUse != null) { - final DynamicMappingGenerator dynamicMappingGenerator = new DynamicMappingGenerator(this, - generator::writeStartObject, generator::writeEnd, null); - objectConverterToUse.writeJson(o, dynamicMappingGenerator); - dynamicMappingGenerator.flushIfNeeded(); + writeWithObjectConverter(new DynamicMappingGenerator(this, + generator::writeStartObject, generator::writeEnd, null), objectConverterToUse, o); } else { writeItem(itemConverter != null ? itemConverter.from(o) : o, ignoredProperties, isDedup() ? new JsonPointerTracker(jsonPointer, i) : null);