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 2022/01/25 16:18:02 UTC

[johnzon] branch master updated: [JOHNZON-358][JOHNZON-357] enhance (de)serializer support on list/map types

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 <rm...@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);