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 2019/08/11 19:05:07 UTC

[johnzon] branch master updated: JOHNZON-242 ensure arrays of objects work with serializers

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 7680b84  JOHNZON-242 ensure arrays of objects work with serializers
7680b84 is described below

commit 7680b84a774814f92dfe46d3b8a7c908ba77680b
Author: Romain Manni-Bucau <rm...@apache.org>
AuthorDate: Sun Aug 11 21:04:51 2019 +0200

    JOHNZON-242 ensure arrays of objects work with serializers
---
 .../org/apache/johnzon/jsonb/JsonbAccessMode.java  |  28 +-
 .../org/apache/johnzon/jsonb/SerializerTest.java   | 284 +++++++++++++++++++++
 .../johnzon/mapper/DynamicMappingGenerator.java    | 133 +++++++---
 .../johnzon/mapper/MappingGeneratorImpl.java       |  24 +-
 .../org/apache/johnzon/mapper/ObjectConverter.java |  14 +
 5 files changed, 438 insertions(+), 45 deletions(-)

diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
index d430960..0fa1ef0 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
@@ -83,6 +83,7 @@ import javax.json.bind.config.PropertyVisibilityStrategy;
 import javax.json.bind.serializer.JsonbDeserializer;
 import javax.json.bind.serializer.JsonbSerializer;
 import javax.json.spi.JsonProvider;
+import javax.json.stream.JsonGenerator;
 import javax.json.stream.JsonParserFactory;
 
 import org.apache.johnzon.core.Types;
@@ -102,6 +103,7 @@ import org.apache.johnzon.mapper.Converter;
 import org.apache.johnzon.mapper.JohnzonAny;
 import org.apache.johnzon.mapper.JohnzonConverter;
 import org.apache.johnzon.mapper.MapperConverter;
+import org.apache.johnzon.mapper.MappingGenerator;
 import org.apache.johnzon.mapper.MappingParser;
 import org.apache.johnzon.mapper.ObjectConverter;
 import org.apache.johnzon.mapper.TypeAwareAdapter;
@@ -833,16 +835,23 @@ public class JsonbAccessMode implements AccessMode, Closeable {
                 final Class<?> mappedType = types.findParamType(pt, JsonbDeserializer.class);
                 toRelease.add(instance);
                 final JsonBuilderFactory builderFactoryInstance = builderFactory.get();
+                final Type[] arguments = types.findParameterizedType(value, JsonbDeserializer.class).getActualTypeArguments();
+                final boolean global = arguments.length == 1 && arguments[0] != null && arguments[0].equals(annotationHolder.getType());
                 reader = new ObjectConverter.Reader() {
                     private final ConcurrentMap<Type, BiFunction<JsonValue, MappingParser, Object>> impl =
                             new ConcurrentHashMap<>();
 
                     @Override
+                    public boolean isGlobal() {
+                        return global;
+                    }
+
+                    @Override
                     public Object fromJson(final JsonValue value,
                                            final Type targetType,
                                            final MappingParser parser) {
                         final JsonbDeserializer jsonbDeserializer = instance.getValue();
-                        if (targetType == mappedType) { // fast test and matches most cases
+                        if (global || targetType == mappedType) { // fast test and matches most cases
                             return mapItem(value, targetType, parser, jsonbDeserializer);
                         }
 
@@ -921,8 +930,21 @@ public class JsonbAccessMode implements AccessMode, Closeable {
                 final Class<? extends JsonbSerializer> value = serializer.value();
                 final JohnzonAdapterFactory.Instance<? extends JsonbSerializer> instance = newInstance(value);
                 toRelease.add(instance);
-                writer = (instance1, jsonbGenerator) ->
-                        instance.getValue().serialize(instance1, jsonbGenerator.getJsonGenerator(), new JohnzonSerializationContext(jsonbGenerator));
+                final Type[] arguments = types.findParameterizedType(value, JsonbSerializer.class).getActualTypeArguments();
+                final boolean global = arguments.length == 1 && arguments[0] != null && arguments[0].equals(reader.getType());
+                final JsonbSerializer jsonbSerializer = instance.getValue();
+                writer = new ObjectConverter.Writer() {
+                    @Override
+                    public void writeJson(final Object instance, final MappingGenerator jsonbGenerator) {
+                        final JsonGenerator generator = jsonbGenerator.getJsonGenerator();
+                        jsonbSerializer.serialize(instance, generator, new JohnzonSerializationContext(jsonbGenerator));
+                    }
+
+                    @Override
+                    public boolean isGlobal() {
+                        return global;
+                    }
+                };
             } else if (johnzonConverter != null) {
                 try {
                     MapperConverter mapperConverter = johnzonConverter.value().newInstance();
diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/SerializerTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/SerializerTest.java
index 7945835..e535b54 100644
--- a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/SerializerTest.java
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/SerializerTest.java
@@ -28,10 +28,12 @@ import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.UUID;
 
 import javax.json.bind.Jsonb;
 import javax.json.bind.JsonbBuilder;
+import javax.json.bind.JsonbConfig;
 import javax.json.bind.annotation.JsonbTransient;
 import javax.json.bind.annotation.JsonbTypeDeserializer;
 import javax.json.bind.annotation.JsonbTypeSerializer;
@@ -171,6 +173,288 @@ public class SerializerTest {
         jsonb.close();
     }
 
+    @Test
+    public void fromConfig() throws Exception {
+        try (final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()
+                .withSerializers(new AnimalSerializer()).withDeserializers(new AnimalDeserializer()))) {
+            final Animals animals = new Animals();
+            animals.animals.add(new Cat(5, "Garfield", 10.5f, true, true));
+            animals.animals.add(new Dog(3, "Milo", 5.5f, false, true));
+            animals.animals.add(new Animal(6, "Tweety", 0.5f, false));
+
+            final String jsonString = jsonb.toJson(animals);
+            assertEquals("{\"animals\":[" +
+                    "{\"type\":\"cat\",\"cuddly\":true,\"age\":5,\"furry\":true,\"name\":\"Garfield\",\"weight\":10.5}," +
+                    "{\"type\":\"dog\",\"barking\":true,\"age\":3,\"furry\":false,\"name\":\"Milo\",\"weight\":5.5}," +
+                    "{\"type\":\"animal\",\"age\":6,\"furry\":false,\"name\":\"Tweety\",\"weight\":0.5}]}",
+                    jsonString);
+
+            final Animals deserialized = jsonb.fromJson("{ \"animals\" : [ "
+                    + "{ \"type\" : \"cat\", \"cuddly\" : true, \"age\" : 5, \"furry\" : true, \"name\" : \"Garfield\" , \"weight\" : 10.5}, "
+                    + "{ \"type\" : \"dog\", \"barking\" : true, \"age\" : 3, \"furry\" : false, \"name\" : \"Milo\", \"weight\" : 5.5}, "
+                    + "{ \"type\" : \"animal\", \"age\" : 6, \"furry\" : false, \"name\" : \"Tweety\", \"weight\" : 0.5}"
+                    + " ] }", Animals.class);
+            assertEquals(animals.animals, deserialized.animals);
+        }
+    }
+
+    @Test
+    public void fromAnnotation() {
+        final AnimalsAnnotation animals = new AnimalsAnnotation();
+        animals.animals.add(new Cat(5, "Garfield", 10.5f, true, true));
+        animals.animals.add(new Dog(3, "Milo", 5.5f, false, true));
+        animals.animals.add(new Animal(6, "Tweety", 0.5f, false));
+
+        final String jsonString = jsonb.toJson(animals);
+        assertEquals("{\"animals\":[" +
+                "{\"type\":\"cat\",\"cuddly\":true,\"age\":5,\"furry\":true,\"name\":\"Garfield\",\"weight\":10.5}," +
+                "{\"type\":\"dog\",\"barking\":true,\"age\":3,\"furry\":false,\"name\":\"Milo\",\"weight\":5.5}," +
+                "{\"type\":\"animal\",\"age\":6,\"furry\":false,\"name\":\"Tweety\",\"weight\":0.5}]}", jsonString);
+
+        final AnimalsAnnotation deserialized = jsonb.fromJson("{ \"animals\" : [ "
+                + "{ \"type\" : \"cat\", \"cuddly\" : true, \"age\" : 5, \"furry\" : true, \"name\" : \"Garfield\" , \"weight\" : 10.5}, "
+                + "{ \"type\" : \"dog\", \"barking\" : true, \"age\" : 3, \"furry\" : false, \"name\" : \"Milo\", \"weight\" : 5.5}, "
+                + "{ \"type\" : \"animal\", \"age\" : 6, \"furry\" : false, \"name\" : \"Tweety\", \"weight\" : 0.5}"
+                + " ] }", AnimalsAnnotation.class);
+        assertEquals(animals.animals, deserialized.animals);
+    }
+
+    public static class Animal {
+        public int age;
+        public String name;
+        public float weight;
+        public boolean furry;
+
+        public Animal() {
+            // no-op
+        }
+
+        public Animal(int age, String name, float weight, boolean furry) {
+            this.age = age;
+            this.name = name;
+            this.weight = weight;
+            this.furry = furry;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            final Animal animal = (Animal) o;
+            return age == animal.age &&
+                    Float.compare(animal.weight, weight) == 0 &&
+                    furry == animal.furry &&
+                    Objects.equals(name, animal.name);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(age, name, weight, furry);
+        }
+    }
+
+    public static class Dog extends Animal {
+        public boolean barking;
+
+        public Dog() {
+            // no-op
+        }
+
+        public Dog(int age, String name, float weight, boolean furry,
+                   boolean barking) {
+            super(age, name, weight, furry);
+            this.barking = barking;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            if (!super.equals(o)) {
+                return false;
+            }
+            final Dog dog = (Dog) o;
+            return barking == dog.barking;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(super.hashCode(), barking);
+        }
+    }
+
+    public static class Cat extends Animal {
+        public boolean cuddly;
+
+        public Cat() {
+            // no-op
+        }
+
+        public Cat(int age, String name, float weight, boolean furry,
+                   boolean cuddly) {
+            super(age, name, weight, furry);
+            this.cuddly = cuddly;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            if (!super.equals(o)) {
+                return false;
+            }
+            final Cat cat = (Cat) o;
+            return cuddly == cat.cuddly;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(super.hashCode(), cuddly);
+        }
+    }
+
+    public static class AnimalListDeserializer implements JsonbDeserializer<List<Animal>> {
+        private final AnimalDeserializer animalDeserializer = new AnimalDeserializer();
+
+        @Override
+        public List<Animal> deserialize(final JsonParser parser,
+                                        final DeserializationContext ctx,
+                                        final Type type) {
+            final List<Animal> animals = new ArrayList<>();
+            while (parser.hasNext()) {
+                JsonParser.Event event = parser.next();
+                while (event == JsonParser.Event.START_OBJECT) {
+                    animals.add(animalDeserializer.deserialize(parser, ctx, type));
+                    event = parser.next();
+                }
+            }
+            return animals;
+        }
+    }
+
+    public static class AnimalListSerializer implements JsonbSerializer<List<Animal>> {
+        private final AnimalSerializer animalSerializer = new AnimalSerializer();
+
+        @Override
+        public void serialize(final List<Animal> animals, final JsonGenerator gen,
+                              final SerializationContext ctx) {
+            gen.writeStartArray();
+            for (final Animal animal : animals) {
+                animalSerializer.serialize(animal, gen, ctx);
+            }
+            gen.writeEnd();
+        }
+    }
+
+    public static class AnimalsAnnotation {
+        @JsonbTypeSerializer(AnimalListSerializer.class)
+        @JsonbTypeDeserializer(AnimalListDeserializer.class)
+        public List<Animal> animals = new ArrayList<>();
+    }
+
+    public static class Animals {
+        public List<Animal> animals = new ArrayList<>();
+    }
+
+    public static class AnimalDeserializer implements JsonbDeserializer<Animal> {
+        @Override
+        public Animal deserialize(final JsonParser jsonParser,
+                                  final DeserializationContext deserializationContext,
+                                  final Type type) {
+            Animal animal = null;
+            while (jsonParser.hasNext()) {
+                JsonParser.Event event = jsonParser.next();
+                if (event == JsonParser.Event.START_OBJECT) {
+                    continue;
+                }
+                if (event == JsonParser.Event.END_OBJECT) {
+                    break;
+                }
+                if (event == JsonParser.Event.KEY_NAME) {
+                    switch (jsonParser.getString()) {
+                        case "type":
+                            jsonParser.next();
+                            switch (jsonParser.getString()) {
+                                case "cat":
+                                    animal = new Cat();
+                                    break;
+                                case "dog":
+                                    animal = new Dog();
+                                    break;
+                                default:
+                                    animal = new Animal();
+                            }
+                            break;
+                        case "name":
+                            jsonParser.next();
+                            animal.name = jsonParser.getString();
+                            break;
+                        case "age":
+                            jsonParser.next();
+                            animal.age = jsonParser.getInt();
+                            break;
+                        case "furry":
+                            event = jsonParser.next();
+                            animal.furry = event == JsonParser.Event.VALUE_TRUE;
+                            break;
+                        case "weight":
+                            jsonParser.next();
+                            animal.weight = jsonParser.getBigDecimal().floatValue();
+                            break;
+                        case "cuddly":
+                            event = jsonParser.next();
+                            ((Cat) animal).cuddly = event == JsonParser.Event.VALUE_TRUE;
+                            break;
+                        case "barking":
+                            event = jsonParser.next();
+                            ((Dog) animal).barking = event == JsonParser.Event.VALUE_TRUE;
+                            break;
+                        default:
+                    }
+                }
+            }
+            return animal;
+        }
+    }
+
+    public static class AnimalSerializer implements JsonbSerializer<Animal> {
+        @Override
+        public void serialize(final Animal animal, final JsonGenerator jsonGenerator,
+                              final SerializationContext serializationContext) {
+            if (animal != null) {
+                jsonGenerator.writeStartObject();
+                if (Cat.class.isAssignableFrom(animal.getClass())) {
+                    jsonGenerator.write("type", "cat");
+                    jsonGenerator.write("cuddly", ((Cat) animal).cuddly);
+                } else if (Dog.class.isAssignableFrom(animal.getClass())) {
+                    jsonGenerator.write("type", "dog");
+                    jsonGenerator.write("barking", ((Dog) animal).barking);
+                } else {
+                    jsonGenerator.write("type", "animal");
+                }
+                jsonGenerator.write("age", animal.age);
+                jsonGenerator.write("furry", animal.furry);
+                jsonGenerator.write("name", animal.name);
+                jsonGenerator.write("weight", animal.weight);
+                jsonGenerator.writeEnd();
+            } else {
+                serializationContext.serialize(null, jsonGenerator);
+            }
+        }
+    }
+
     public static class StringHolder implements Holder<String> {
         private String instance = "Test";
 
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 5e7c5c3..bbbb8f0 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
@@ -66,14 +66,15 @@ public class DynamicMappingGenerator implements MappingGenerator {
     }
 
     public void flushIfNeeded() {
-        if (this.generator.state == WritingState.WROTE_START_OBJECT) {
+        if (this.generator.state == WritingState.WROTE_START) {
             writeEnd.run();
             this.generator.state = WritingState.NONE;
         }
     }
 
     private enum WritingState {
-        NONE, WROTE_START_OBJECT,
+        NONE,
+        WROTE_START,
         DONT_WRITE_END
     }
 
@@ -81,7 +82,8 @@ public class DynamicMappingGenerator implements MappingGenerator {
         private final JsonGenerator delegate;
         private final Runnable writeStart;
         private final String keyIfNoObject;
-        private WritingState state = WritingState.NONE;
+        private WritingState state = WritingState.NONE; // todo: we need a stack (linkedlist) here to be accurate
+        private int nested = 0;
 
         private InObjectOrPrimitiveJsonGenerator(final JsonGenerator generator, final Runnable writeStart,
                                                  final String keyName) {
@@ -91,184 +93,239 @@ public class DynamicMappingGenerator implements MappingGenerator {
         }
 
         private void ensureStart() {
-            if (state == WritingState.WROTE_START_OBJECT) {
+            if (state != WritingState.NONE) {
                 return;
             }
             writeStart.run();
-            state = WritingState.WROTE_START_OBJECT;
+            state = WritingState.WROTE_START;
         }
 
         @Override
         public JsonGenerator writeStartObject() {
-            // return delegate.writeStartObject();
+            if (state == WritingState.NONE) {
+                ensureStart();
+            } else {
+                nested++;
+                delegate.writeStartObject();
+            }
             return this;
         }
 
         @Override
         public JsonGenerator writeStartObject(final String name) {
+            if (state != WritingState.NONE) {
+                nested++;
+            }
             ensureStart();
-            return delegate.writeStartObject(name);
+            delegate.writeStartObject(name);
+            return this;
         }
 
         @Override
         public JsonGenerator writeStartArray() {
+            if (state != WritingState.NONE) {
+                nested++;
+            }
             if (keyIfNoObject != null && state == WritingState.NONE) {
                 state = WritingState.DONT_WRITE_END; // skip writeEnd since the impl will do it
                 return delegate.writeStartArray(keyIfNoObject);
+            } else if (state == WritingState.NONE) {
+                ensureStart();
+                return this;
             }
-            return delegate.writeStartArray();
+            delegate.writeStartArray();
+            return this;
         }
 
         @Override
         public JsonGenerator writeStartArray(final String name) {
+            if (state != WritingState.NONE) {
+                nested++;
+            }
             ensureStart();
-            return delegate.writeStartArray(name);
+            delegate.writeStartArray(name);
+            return this;
         }
 
         @Override
         public JsonGenerator writeKey(final String name) {
             ensureStart();
-            return delegate.writeKey(name);
+            delegate.writeKey(name);
+            return this;
         }
 
         @Override
         public JsonGenerator write(final String name, final JsonValue value) {
             ensureStart();
-            return delegate.write(name, value);
+            delegate.write(name, value);
+            return this;
         }
 
         @Override
         public JsonGenerator write(final String name, final String value) {
             ensureStart();
-            return delegate.write(name, value);
+            delegate.write(name, value);
+            return this;
         }
 
         @Override
         public JsonGenerator write(final String name, final BigInteger value) {
             ensureStart();
-            return delegate.write(name, value);
+            delegate.write(name, value);
+            return this;
         }
 
         @Override
         public JsonGenerator write(final String name, final BigDecimal value) {
             ensureStart();
-            return delegate.write(name, value);
+            delegate.write(name, value);
+            return this;
         }
 
         @Override
         public JsonGenerator write(final String name, final int value) {
             ensureStart();
-            return delegate.write(name, value);
+            delegate.write(name, value);
+            return this;
         }
 
         @Override
         public JsonGenerator write(final String name, final long value) {
             ensureStart();
-            return delegate.write(name, value);
+            delegate.write(name, value);
+            return this;
         }
 
         @Override
         public JsonGenerator write(final String name, final double value) {
             ensureStart();
-            return delegate.write(name, value);
+            delegate.write(name, value);
+            return this;
         }
 
         @Override
         public JsonGenerator write(final String name, final boolean value) {
             ensureStart();
-            return delegate.write(name, value);
+            delegate.write(name, value);
+            return this;
         }
 
         @Override
         public JsonGenerator writeNull(final String name) {
             ensureStart();
-            return delegate.writeNull(name);
+            delegate.writeNull(name);
+            return this;
         }
 
         @Override
         public JsonGenerator writeEnd() {
-            return delegate.writeEnd();
+            if (nested == 0 && state == WritingState.WROTE_START) {
+                state = WritingState.NONE;
+            }
+            if (nested > 0) {
+                nested--;
+            }
+            delegate.writeEnd();
+            return this;
         }
 
         @Override
         public JsonGenerator write(final JsonValue value) {
             if (isWritingPrimitive()) {
                 state = WritingState.DONT_WRITE_END;
-                return delegate.write(keyIfNoObject, value);
+                delegate.write(keyIfNoObject, value);
+                return this;
             }
-            return delegate.write(value);
+            delegate.write(value);
+            return this;
         }
 
         @Override
         public JsonGenerator write(final String value) {
             if (isWritingPrimitive()) {
                 state = WritingState.DONT_WRITE_END;
-                return delegate.write(keyIfNoObject, value);
+                delegate.write(keyIfNoObject, value);
+                return this;
             }
-            return delegate.write(value);
+            delegate.write(value);
+            return this;
         }
 
         @Override
         public JsonGenerator write(final BigDecimal value) {
             if (isWritingPrimitive()) {
                 state = WritingState.DONT_WRITE_END;
-                return delegate.write(keyIfNoObject, value);
+                delegate.write(keyIfNoObject, value);
+                return this;
             }
-            return delegate.write(value);
+            delegate.write(value);
+            return this;
         }
 
         @Override
         public JsonGenerator write(final BigInteger value) {
             if (isWritingPrimitive()) {
                 state = WritingState.DONT_WRITE_END;
-                return delegate.write(keyIfNoObject, value);
+                delegate.write(keyIfNoObject, value);
+                return this;
             }
-            return delegate.write(value);
+            delegate.write(value);
+            return this;
         }
 
         @Override
         public JsonGenerator write(final int value) {
             if (isWritingPrimitive()) {
                 state = WritingState.DONT_WRITE_END;
-                return delegate.write(keyIfNoObject, value);
+                delegate.write(keyIfNoObject, value);
+                return this;
             }
-            return delegate.write(value);
+            delegate.write(value);
+            return this;
         }
 
         @Override
         public JsonGenerator write(final long value) {
             if (isWritingPrimitive()) {
                 state = WritingState.DONT_WRITE_END;
-                return delegate.write(keyIfNoObject, value);
+                delegate.write(keyIfNoObject, value);
+                return this;
             }
-            return delegate.write(value);
+            delegate.write(value);
+            return this;
         }
 
         @Override
         public JsonGenerator write(final double value) {
             if (isWritingPrimitive()) {
                 state = WritingState.DONT_WRITE_END;
-                return delegate.write(keyIfNoObject, value);
+                delegate.write(keyIfNoObject, value);
+                return this;
             }
-            return delegate.write(value);
+            delegate.write(value);
+            return this;
         }
 
         @Override
         public JsonGenerator write(boolean value) {
             if (isWritingPrimitive()) {
                 state = WritingState.DONT_WRITE_END;
-                return delegate.write(keyIfNoObject, value);
+                delegate.write(keyIfNoObject, value);
+                return this;
             }
-            return delegate.write(value);
+            delegate.write(value);
+            return this;
         }
 
         @Override
         public JsonGenerator writeNull() {
             if (isWritingPrimitive()) {
                 state = WritingState.DONT_WRITE_END;
-                return delegate.writeNull(keyIfNoObject);
+                delegate.writeNull(keyIfNoObject);
+                return this;
             }
-            return delegate.writeNull();
+            delegate.writeNull();
+            return this;
         }
 
         private boolean isWritingPrimitive() {
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 1d6119d..ca1c049 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
@@ -19,6 +19,7 @@
 package org.apache.johnzon.mapper;
 
 import static java.util.Collections.emptyList;
+import static java.util.stream.Collectors.toList;
 
 import org.apache.johnzon.mapper.internal.JsonPointerTracker;
 import org.apache.johnzon.mapper.util.ArrayUtil;
@@ -33,8 +34,12 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
+import java.util.Spliterator;
+import java.util.Spliterators;
 import java.util.stream.BaseStream;
+import java.util.stream.StreamSupport;
 
 public class MappingGeneratorImpl implements MappingGenerator {
     private final MapperConfig config;
@@ -401,7 +406,7 @@ public class MappingGeneratorImpl implements MappingGenerator {
             writeArray(type, itemConverter, key, value, ignoredProperties, jsonPointer);
         } else if ((!dynamic && collection) || (dynamic && Iterable.class.isAssignableFrom(type))) {
             writeIterator(itemConverter, key, objectConverter, ignoredProperties, jsonPointer, generator,
-                    Iterable.class.cast(value).iterator());
+                    Iterable.class.cast(value).iterator(), value);
         } else if ((!dynamic && map) || (dynamic && Map.class.isAssignableFrom(type))) {
             generator.writeStartObject(key);
             writeMapBody((Map<?, ?>) value, itemConverter);
@@ -417,10 +422,10 @@ public class MappingGeneratorImpl implements MappingGenerator {
             }
         } else if (BaseStream.class.isAssignableFrom(type)) {
             writeIterator(itemConverter, key, objectConverter, ignoredProperties, jsonPointer, generator,
-                    BaseStream.class.cast(value).iterator());
+                    BaseStream.class.cast(value).iterator(), value);
         } else if (Iterator.class.isAssignableFrom(type)) {
             writeIterator(itemConverter, key, objectConverter, ignoredProperties, jsonPointer, generator,
-                    Iterator.class.cast(value));
+                    Iterator.class.cast(value), value);
         } else {
             if (objectConverter != null) {
                 final DynamicMappingGenerator dynamicMappingGenerator = new DynamicMappingGenerator(this,
@@ -466,7 +471,18 @@ public class MappingGeneratorImpl implements MappingGenerator {
                                final Collection<String> ignoredProperties,
                                final JsonPointerTracker jsonPointer,
                                final JsonGenerator generator,
-                               final Iterator<?> iterator) {
+                               final Iterator<?> iterator,
+                               final Object originalValue) {
+        if (objectConverter != null && objectConverter.isGlobal()) {
+            final List<Object> list = List.class.isInstance(originalValue) ?
+                    List.class.cast(originalValue) :
+                    StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.IMMUTABLE), false)
+                        .collect(toList());
+            objectConverter.writeJson(list, new DynamicMappingGenerator(
+                    this, generator::writeStartArray, generator::writeEnd, key));
+            return;
+        }
+
         int i = 0;
         generator.writeStartArray(key);
         while (iterator.hasNext()) {
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/ObjectConverter.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/ObjectConverter.java
index a468c73..673585d 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/ObjectConverter.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/ObjectConverter.java
@@ -37,12 +37,26 @@ public final class ObjectConverter {
 
     public interface Writer<T> extends MapperConverter {
         void writeJson(T instance, MappingGenerator jsonbGenerator);
+
+        // returns true if it is for containers - if any - and not each container item (ex: list)
+        default boolean isGlobal() {
+            return false;
+        }
     }
 
     public interface Reader<T> extends MapperConverter {
         T fromJson(JsonValue jsonValue, Type targetType, MappingParser parser);
+
+        // returns true if it is for containers - if any - and not each container item (ex: list)
+        default boolean isGlobal() {
+            return false;
+        }
     }
 
     public interface Codec<T> extends ObjectConverter.Writer<T>, ObjectConverter.Reader<T> {
+        @Override
+        default boolean isGlobal() {
+            return false;
+        }
     }
 }