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 aaa2af9 [JOHNZON-337] avoid jsonb deserializers to loop
aaa2af9 is described below
commit aaa2af985b51517f6d636d3a28b176f3109c5958
Author: Romain Manni-Bucau <[email protected]>
AuthorDate: Fri Mar 5 14:41:14 2021 +0100
[JOHNZON-337] avoid jsonb deserializers to loop
---
.../apache/johnzon/RecursivePolymorphismTest.java | 131 +++++++++++++++++++++
.../org/apache/johnzon/mapper/MappingParser.java | 6 +-
.../apache/johnzon/mapper/MappingParserImpl.java | 66 +++++++----
3 files changed, 182 insertions(+), 21 deletions(-)
diff --git
a/johnzon-jsonb/src/test/java/org/apache/johnzon/RecursivePolymorphismTest.java
b/johnzon-jsonb/src/test/java/org/apache/johnzon/RecursivePolymorphismTest.java
new file mode 100644
index 0000000..75996ca
--- /dev/null
+++
b/johnzon-jsonb/src/test/java/org/apache/johnzon/RecursivePolymorphismTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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;
+
+import org.junit.Test;
+
+import javax.json.bind.Jsonb;
+import javax.json.bind.JsonbBuilder;
+import javax.json.bind.JsonbConfig;
+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.lang.reflect.Type;
+
+import static javax.json.stream.JsonParser.Event.KEY_NAME;
+import static javax.json.stream.JsonParser.Event.START_OBJECT;
+import static javax.json.stream.JsonParser.Event.VALUE_NUMBER;
+import static org.junit.Assert.assertEquals;
+
+public class RecursivePolymorphismTest {
+ @Test
+ public void read() throws Exception {
+ try (final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()
+ .withDeserializers(new PolyDeserializer()))) {
+ final Parent parent =
jsonb.fromJson("{\"type\":1,\"name\":\"first\",\"uno\":true,\"duo\":true}",
Parent.class);
+ assertEquals("Child1{name='first', uno=true}", parent.toString());
+ }
+ }
+
+ @Test
+ public void write() throws Exception {
+ final Child1 child1 = new Child1();
+ child1.name = "first";
+ child1.uno = true;
+ try (final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()
+ .withSerializers(new PolySerializer()))) {
+ final String json = jsonb.toJson(child1);
+ assertEquals("{\"type\":1,\"name\":\"first\",\"uno\":true}", json);
+ }
+ }
+
+ public static class Parent {
+ public String name;
+
+ @Override
+ public String toString() {
+ return "Parent{name='" + name + "'}";
+ }
+ }
+
+ public static class Child1 extends Parent {
+ public boolean uno;
+
+ @Override
+ public String toString() {
+ return "Child1{name='" + name + "', uno=" + uno + '}';
+ }
+ }
+
+ public static class Child2 extends Parent {
+ public boolean duo;
+
+ @Override
+ public String toString() {
+ return "Child2{name='" + name + "', duo=" + duo + '}';
+ }
+ }
+
+ public static class PolySerializer implements JsonbSerializer<Parent> {
+ @Override
+ public void serialize(final Parent obj, final JsonGenerator generator,
final SerializationContext ctx) {
+ generator.writeStartObject();
+ generator.write("type", Child1.class.isInstance(obj) ? 1 : 2);
+ ctx.serialize(obj, generator);
+ generator.writeEnd();
+ }
+ }
+
+ public static class PolyDeserializer implements JsonbDeserializer<Parent> {
+ @Override
+ public Parent deserialize(final JsonParser parser,
+ final DeserializationContext ctx,
+ final Type rtType) {
+ moveToType(parser);
+ final int type = parser.getInt();
+ switch (type) {
+ case 1:
+ return ctx.deserialize(Child1.class, parser);
+ case 2:
+ return ctx.deserialize(Child2.class, parser);
+ default:
+ throw new IllegalArgumentException(String.valueOf(type));
+ }
+ }
+
+ private void moveToType(final JsonParser parser) {
+ ensureNext(parser, START_OBJECT);
+ ensureNext(parser, KEY_NAME);
+ if (!"type".equals(parser.getString())) {
+ throw new IllegalArgumentException("Expected 'type' but got '"
+ parser.getString() + "'");
+ }
+ ensureNext(parser, VALUE_NUMBER);
+ }
+
+ private void ensureNext(final JsonParser parser, final
JsonParser.Event expected) {
+ final JsonParser.Event next = parser.next();
+ if (expected != next) {
+ throw new IllegalArgumentException(next + " != " + expected);
+ }
+ }
+ }
+}
diff --git
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParser.java
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParser.java
index c129264..7a265a6 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParser.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParser.java
@@ -20,14 +20,18 @@ package org.apache.johnzon.mapper;
import javax.json.JsonValue;
import java.lang.reflect.Type;
+import java.util.Collection;
/**
* Handles reading Json for Objects.
- *
*/
public interface MappingParser {
<T> T readObject(Type targetType);
<T> T readObject(JsonValue jsonValue, Type targetType);
+
+ default Collection<Class<?>> getSkippedConverters() {
+ return null;
+ }
}
diff --git
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
index 71e0848..80ca4ad 100644
---
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
+++
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
@@ -136,18 +136,22 @@ public class MappingParserImpl implements MappingParser {
}
@Override
- public <T> T readObject(JsonValue jsonValue, Type targetType) {
- return readObject(jsonValue, targetType, targetType instanceof Class
|| targetType instanceof ParameterizedType);
+ public <T> T readObject(final JsonValue jsonValue, final Type targetType) {
+ return readObject(jsonValue, targetType, targetType instanceof Class
|| targetType instanceof ParameterizedType, null);
}
- private <T> T readObject(JsonValue jsonValue, Type targetType, boolean
applyObjectConverter) {
+ public <T> T readObject(final JsonValue jsonValue, final Type targetType,
final boolean applyObjectConverter,
+ final Collection<Class<?>> skippedConverters) {
final JsonValue.ValueType valueType = jsonValue != null ?
jsonValue.getValueType() : null;
if (JsonStructure.class == targetType || JsonObject.class ==
targetType || JsonValue.class == targetType) {
return (T) jsonValue;
}
if (JsonObject.class.isInstance(jsonValue)) {
- return (T) buildObject(targetType,
JsonObject.class.cast(jsonValue), applyObjectConverter, isDeduplicateObjects ?
new JsonPointerTracker(null, "/") : null);
+ return (T) buildObject(
+ targetType, JsonObject.class.cast(jsonValue),
applyObjectConverter,
+ isDeduplicateObjects ? new JsonPointerTracker(null, "/") :
null,
+ skippedConverters);
}
if (JsonString.class.isInstance(jsonValue)) {
if ((targetType == String.class || targetType == Object.class)) {
@@ -221,7 +225,7 @@ public class MappingParserImpl implements MappingParser {
isDeduplicateObjects ? new
JsonPointerTracker(null, "/") : null, Object.class);
}
if (Collection.class.isAssignableFrom(asClass)) {
- return readObject(jsonValue, new
JohnzonParameterizedType(asClass, Object.class), applyObjectConverter);
+ return readObject(jsonValue, new
JohnzonParameterizedType(asClass, Object.class), applyObjectConverter,
skippedConverters);
}
}
if (ParameterizedType.class.isInstance(targetType)) {
@@ -256,7 +260,7 @@ public class MappingParserImpl implements MappingParser {
private Object buildObject(final Type inType, final JsonObject object,
final boolean applyObjectConverter,
- final JsonPointerTracker jsonPointer) {
+ final JsonPointerTracker jsonPointer, final
Collection<Class<?>> skippedConverters) {
Type type = inType;
if (inType == Object.class) {
type = new JohnzonParameterizedType(Map.class, String.class,
Object.class);
@@ -268,9 +272,16 @@ public class MappingParserImpl implements MappingParser {
throw new MapperException("ObjectConverters are only supported
for Classes not Types");
}
- ObjectConverter.Reader objectConverter =
config.findObjectConverterReader((Class) type);
- if (objectConverter != null) {
- return objectConverter.fromJson(object, type, new
SuppressConversionMappingParser(this, object));
+ final Class clazz = (Class) type;
+ if (skippedConverters == null ||
!skippedConverters.contains(clazz)) {
+ ObjectConverter.Reader objectConverter =
config.findObjectConverterReader(clazz);
+ if (objectConverter != null) {
+ final Collection<Class<?>> skipped = skippedConverters ==
null ? new ArrayList<>() : skippedConverters;
+ skipped.add(clazz);
+ return objectConverter.fromJson(
+ object, type,
+ new SuppressConversionMappingParser(this, object,
skipped));
+ }
}
}
@@ -280,7 +291,7 @@ public class MappingParserImpl implements MappingParser {
final String discriminator =
object.getString(config.getDiscriminator());
final Class<?> nestedType =
config.getTypeLoader().apply(discriminator);
if (nestedType != null && nestedType != inType) {
- return buildObject(nestedType, object,
applyObjectConverter, jsonPointer);
+ return buildObject(nestedType, object,
applyObjectConverter, jsonPointer, skippedConverters);
}
}
}
@@ -346,8 +357,12 @@ public class MappingParserImpl implements MappingParser {
throw new MapperException("Can't map " + type);
}
- if (applyObjectConverter && classMapping.reader != null) {
- return classMapping.reader.fromJson(object, type, new
SuppressConversionMappingParser(this, object));
+ if (applyObjectConverter && classMapping.reader != null &&
(skippedConverters == null || !skippedConverters.contains(type))) {
+ final Collection<Class<?>> skipped = skippedConverters == null ?
new ArrayList<>() : skippedConverters;
+ if (Class.class.isInstance(type)) { // more than likely, drop this
check?
+ skipped.add(Class.class.cast(type));
+ }
+ return classMapping.reader.fromJson(object, type, new
SuppressConversionMappingParser(this, object, skipped));
}
/* doesn't work yet
if (classMapping.adapter != null) {
@@ -502,7 +517,7 @@ public class MappingParserImpl implements MappingParser {
final Object param;
try {
Type to = key.getTo();
- param = buildObject(to, JsonObject.class.cast(jsonValue), to
instanceof Class, jsonPointer);
+ param = buildObject(to, JsonObject.class.cast(jsonValue), to
instanceof Class, jsonPointer, getSkippedConverters());
} catch (final Exception e) {
throw new MapperException(e);
}
@@ -648,7 +663,7 @@ public class MappingParserImpl implements MappingParser {
baseInstance != null ? baseInstance.getClass() : (
typedAdapter ?
TypeAwareAdapter.class.cast(itemConverter).getTo() : type),
JsonObject.class.cast(jsonValue), type instanceof Class,
- jsonPointer);
+ jsonPointer, getSkippedConverters());
return typedAdapter ? itemConverter.to(object) : object;
} else if (JsonArray.class.isInstance(jsonValue)) {
if (JsonArray.class == type || JsonStructure.class == type ||
JsonValue.class == type) {
@@ -1151,24 +1166,35 @@ public class MappingParserImpl implements MappingParser
{
private static class SuppressConversionMappingParser implements
MappingParser {
private final MappingParserImpl delegate;
private final JsonObject suppressConversionFor;
+ private final Collection<Class<?>> skippedConverters;
- public SuppressConversionMappingParser(MappingParserImpl delegate,
JsonObject suppressConversionFor) {
+ public SuppressConversionMappingParser(final MappingParserImpl
delegate, final JsonObject suppressConversionFor,
+ final Collection<Class<?>>
skippedConverters) {
this.delegate = delegate;
this.suppressConversionFor = suppressConversionFor;
+ this.skippedConverters = skippedConverters;
+ }
+
+ @Override
+ public Collection<Class<?>> getSkippedConverters() {
+ return skippedConverters;
}
@Override
- public <T> T readObject(Type targetType) {
+ public <T> T readObject(final Type targetType) {
return delegate.readObject(targetType);
}
@Override
- public <T> T readObject(JsonValue jsonValue, Type targetType) {
+ public <T> T readObject(final JsonValue jsonValue, final Type
targetType) {
+ final Collection<Class<?>> skippedConverters =
getSkippedConverters();
if (suppressConversionFor == jsonValue) {
- return delegate.readObject(jsonValue, targetType, false);
+ return delegate.readObject(jsonValue, targetType, false,
skippedConverters);
}
- return delegate.readObject(jsonValue, targetType);
+ final boolean useConverters = (Class.class.isInstance(targetType)
&&
+ (skippedConverters == null ||
skippedConverters.stream().noneMatch(it ->
it.isAssignableFrom(Class.class.cast(targetType))))) ||
+ ParameterizedType.class.isInstance(targetType);
+ return delegate.readObject(jsonValue, targetType, useConverters,
skippedConverters);
}
}
-
}