You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@johnzon.apache.org by Romain Manni-Bucau <rm...@apache.org> on 2021/02/04 08:50:14 UTC

Fwd: [johnzon] branch master updated: [JOHNZON-335] workaround for nested JsonbSerializers handling and invalid JSON

Hi everyone,

If you are using advanced serializers, can you review this.
I tested with the JIRA case and geronimo-openapi but it stays simple cases
and this change is quite impacting when you abuse of ObjectWriter and/or
JsonbSerializers so a second pair of eyes would be great.

---------- Forwarded message ---------
De : <rm...@apache.org>
Date: jeu. 4 févr. 2021 à 09:47
Subject: [johnzon] branch master updated: [JOHNZON-335] workaround for
nested JsonbSerializers handling and invalid JSON
To: commits@johnzon.apache.org <co...@johnzon.apache.org>


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 1e7c5fd  [JOHNZON-335] workaround for nested JsonbSerializers
handling and invalid JSON
1e7c5fd is described below

commit 1e7c5fda86789f78a98b21dfecacb826854819e0
Author: Romain Manni-Bucau <rm...@gmail.com>
AuthorDate: Thu Feb 4 09:46:20 2021 +0100

    [JOHNZON-335] workaround for nested JsonbSerializers handling and
invalid JSON
---
 .../org/apache/johnzon/core/JsonGeneratorImpl.java |   2 +-
 .../org/apache/johnzon/jsonb/SerializerTest.java   |  33 +++
 .../johnzon/mapper/DynamicMappingGenerator.java    | 232
++++++++++++++++++++-
 .../johnzon/mapper/MappingGeneratorImpl.java       |   6 +-
 4 files changed, 265 insertions(+), 8 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 852aa2e..7520243 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
@@ -463,7 +463,7 @@ class JsonGeneratorImpl implements JsonGenerator,
JsonChars, Serializable {
         JsonGenerationException ex = null;
         final GeneratorState state = currentState();
         if (state != GeneratorState.END && state !=
GeneratorState.ROOT_VALUE) {
-            ex = new JsonGenerationException("Invalid json");
+            ex = new JsonGenerationException("Invalid json, state=" +
state);
         }
         try {
             if (ex == null) {
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 fe658d5..8a7d86b 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
@@ -55,6 +55,12 @@ public class SerializerTest {
     public final JsonbRule jsonb = new JsonbRule()

 .withPropertyOrderStrategy(PropertyOrderStrategy.LEXICOGRAPHICAL);

+    @Test // https://issues.apache.org/jira/browse/JOHNZON-335
+    public void testNestedSerializer() {
+        final String s = jsonb.toJson(new OuterTestModel());
+        assertEquals("{\"foo\":\"generated in outer
serializer\",\"inner\":{\"bar\":\"generated in inner serializer\"}}", s);
+    }
+
     @Test
     public void passthroughSerializer() {
         final NameHolder nameHolder = new NameHolder();
@@ -770,4 +776,31 @@ public class SerializerTest {
             this.student = student;
         }
     }
+
+    @JsonbTypeSerializer(OuterTestSerializer.class)
+    public static class OuterTestModel {
+    }
+
+    @JsonbTypeSerializer(InnerTestSerializer.class)
+    public static class InnerTestModel {
+    }
+
+    public static class OuterTestSerializer implements
JsonbSerializer<OuterTestModel> {
+        @Override
+        public void serialize(final OuterTestModel obj, final
JsonGenerator generator, final SerializationContext ctx) {
+            generator.writeStartObject();
+            generator.write("foo", "generated in outer serializer");
+            ctx.serialize("inner", new InnerTestModel(), generator);
+            generator.writeEnd();
+        }
+    }
+
+    public static class InnerTestSerializer implements
JsonbSerializer<InnerTestModel> {
+        @Override
+        public void serialize(final InnerTestModel obj, final
JsonGenerator generator, final SerializationContext ctx) {
+            generator.writeStartObject();
+            generator.write("bar", "generated in inner serializer");
+            generator.writeEnd();
+        }
+    }
 }
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 35c6c2e..e1f6f0e 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
@@ -18,11 +18,10 @@
  */
 package org.apache.johnzon.mapper;

-import java.math.BigDecimal;
-import java.math.BigInteger;
-
 import javax.json.JsonValue;
 import javax.json.stream.JsonGenerator;
+import java.math.BigDecimal;
+import java.math.BigInteger;

 public class DynamicMappingGenerator implements MappingGenerator {
     private final MappingGenerator delegate;
@@ -30,7 +29,7 @@ public class DynamicMappingGenerator implements
MappingGenerator {
     private final Runnable writeEnd;
     private final String keyName;

-    private InObjectOrPrimitiveJsonGenerator generator;
+    protected InObjectOrPrimitiveJsonGenerator generator;

     public DynamicMappingGenerator(final MappingGenerator delegate,
                                    final Runnable writeStart,
@@ -42,10 +41,14 @@ public class DynamicMappingGenerator implements
MappingGenerator {
         this.keyName = keyName;
     }

+    protected JsonGenerator getRawJsonGenerator() {
+        return delegate.getJsonGenerator();
+    }
+
     @Override
     public JsonGenerator getJsonGenerator() {
         return generator == null ? generator = new
InObjectOrPrimitiveJsonGenerator(
-                delegate.getJsonGenerator(), writeStart, keyName) :
generator;
+                getRawJsonGenerator(), writeStart, keyName) : generator;
     }

     @Override
@@ -61,10 +64,15 @@ public class DynamicMappingGenerator implements
MappingGenerator {
     private JsonGenerator ensureGenerator(final JsonGenerator generator) {
         if (this.generator != null && this.generator != generator &&
this.generator.delegate != generator) {
             this.generator = null;
+            reset();
         }
         return getJsonGenerator(); // ensure we wrap it
     }

+    protected void reset() {
+        // no-op
+    }
+
     public void flushIfNeeded() {
         if (this.generator.state == WritingState.WROTE_START) {
             writeEnd.run();
@@ -342,4 +350,218 @@ public class DynamicMappingGenerator implements
MappingGenerator {
             delegate.flush();
         }
     }
+
+    private static abstract class DelegatingGenerator implements
JsonGenerator {
+        protected final JsonGenerator delegate;
+
+        protected DelegatingGenerator(final JsonGenerator generator) {
+            this.delegate = generator;
+        }
+
+        @Override
+        public JsonGenerator writeKey(final String name) {
+            delegate.writeKey(name);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator write(final String name, final JsonValue
value) {
+            delegate.write(name, value);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator write(final String name, final String value) {
+            delegate.write(name, value);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator write(final String name, final BigInteger
value) {
+            delegate.write(name, value);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator write(final String name, final BigDecimal
value) {
+            delegate.write(name, value);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator write(final String name, final int value) {
+            delegate.write(name, value);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator write(final String name, final long value) {
+            delegate.write(name, value);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator write(final String name, final double value) {
+            delegate.write(name, value);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator write(final String name, final boolean value)
{
+            delegate.write(name, value);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator writeNull(final String name) {
+            delegate.writeNull(name);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator write(final JsonValue value) {
+            delegate.write(value);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator write(final String value) {
+            delegate.write(value);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator write(final BigDecimal value) {
+            delegate.write(value);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator write(final BigInteger value) {
+            delegate.write(value);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator write(final int value) {
+            delegate.write(value);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator write(final long value) {
+            delegate.write(value);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator write(final double value) {
+            delegate.write(value);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator write(boolean value) {
+            delegate.write(value);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator writeNull() {
+            delegate.writeNull();
+            return this;
+        }
+
+        @Override
+        public void close() {
+            delegate.close();
+        }
+
+        @Override
+        public void flush() {
+            delegate.flush();
+        }
+    }
+
+    private static class SkipLastWriteEndGenerator extends
DelegatingGenerator {
+        private int level = -1;
+
+        private SkipLastWriteEndGenerator(final JsonGenerator generator) {
+            super(generator);
+        }
+
+        @Override
+        public JsonGenerator writeStartObject() {
+            level++;
+            if (level > 0) {
+                delegate.writeStartObject();
+            }
+            return this;
+        }
+
+        @Override
+        public JsonGenerator writeStartObject(final String name) {
+            level++;
+            if (level == 0) {
+                level++; // force a writeEnd since it will be a nested
object and not the object we are writing
+            }
+            delegate.writeStartObject(name);
+            return this;
+        }
+
+        @Override
+        public JsonGenerator writeStartArray() {
+            level++;
+            delegate.writeStartArray();
+            return this;
+        }
+
+        @Override
+        public JsonGenerator writeStartArray(final String name) {
+            delegate.writeStartArray(name);
+            level++;
+            return this;
+        }
+
+        @Override
+        public JsonGenerator writeEnd() {
+            if (level > 0) {
+                delegate.writeEnd();
+            }
+            level--;
+            return this;
+        }
+    }
+
+    public static class SkipEnclosingWriteEnd extends
DynamicMappingGenerator {
+        private static final Runnable NOOP = () -> {
+        };
+        private final JsonGenerator rawGenerator;
+
+        private SkipLastWriteEndGenerator skippingGenerator;
+
+        public SkipEnclosingWriteEnd(final MappingGenerator delegate,
final String keyName, final JsonGenerator generator) {
+            super(delegate, NOOP, NOOP, keyName);
+            this.rawGenerator = generator;
+        }
+
+        @Override
+        protected JsonGenerator getRawJsonGenerator() {
+            return rawGenerator;
+        }
+
+        @Override
+        public JsonGenerator getJsonGenerator() {
+            if (skippingGenerator == null) {
+                skippingGenerator = new
SkipLastWriteEndGenerator(super.getJsonGenerator());
+            }
+            return skippingGenerator;
+        }
+
+        @Override
+        protected void reset() {
+            super.reset();
+            skippingGenerator = null;
+        }
+    }
 }
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 0a4ec42..b038f9d 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
@@ -45,7 +45,7 @@ public class MappingGeneratorImpl implements
MappingGenerator {
     private final MapperConfig config;
     private final JsonGenerator generator;
     private final Mappings mappings;
-
+
     private final Boolean isDeduplicateObjects;
     private Map<Object, String> jsonPointers;

@@ -348,7 +348,9 @@ public class MappingGeneratorImpl implements
MappingGenerator {
         }

         if (classMapping.writer != null) {
-            classMapping.writer.writeJson(object, this);
+            final DynamicMappingGenerator gen = new
DynamicMappingGenerator.SkipEnclosingWriteEnd(this, null, generator);
+            classMapping.writer.writeJson(object, gen);
+            gen.flushIfNeeded();
             return;
         }
         if (classMapping.adapter != null) {