fix custom deserializer not working with generic types (#477)
support for generic type and type variable deserialization with custom deserializer
Signed-off-by: Alessandro Moscatelli <alessandro.moscatelli@visiontech.cloud>
diff --git a/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java b/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java
index 280a1e5..91c563b 100644
--- a/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java
+++ b/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -203,10 +203,10 @@
}
}
- if (runtimeType instanceof Class) {
- Class<?> runtimeClass = (Class<?>) runtimeType;
+ Optional<Class<?>> runtimeClass = ReflectionUtils.getOptionalRawType(runtimeType);
+ if (runtimeClass.isPresent()) {
// Check if any interfaces have a match
- for (Class<?> ifc : runtimeClass.getInterfaces()) {
+ for (Class<?> ifc : runtimeClass.get().getInterfaces()) {
ComponentBindings ifcBinding = userComponents.get(ifc);
if (ifcBinding != null) {
Optional<T> match = getMatchingBinding(ifc, ifcBinding, supplier);
@@ -217,7 +217,7 @@
}
// check if the superclass has a match
- Class<?> superClass = runtimeClass.getSuperclass();
+ Class<?> superClass = runtimeClass.get().getSuperclass();
if (superClass != null && superClass != Object.class) {
Optional<T> superBinding = searchComponentBinding(superClass, supplier);
if (superBinding.isPresent()) {
diff --git a/src/main/java/org/eclipse/yasson/internal/ReflectionUtils.java b/src/main/java/org/eclipse/yasson/internal/ReflectionUtils.java
index 4469e96..916ec56 100644
--- a/src/main/java/org/eclipse/yasson/internal/ReflectionUtils.java
+++ b/src/main/java/org/eclipse/yasson/internal/ReflectionUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -62,6 +62,20 @@
return Optional.of((Class<?>) ((ParameterizedType) type).getRawType());
} else if (type instanceof GenericArrayType) {
return Optional.of(((GenericArrayType) type).getClass());
+ } else if (type instanceof TypeVariable) {
+ TypeVariable<?> typeVariable = TypeVariable.class.cast(type);
+ if (Objects.nonNull(typeVariable.getBounds())) {
+ Optional<Class<?>> specializedClass = Optional.empty();
+ for (Type bound : typeVariable.getBounds()) {
+ Optional<Class<?>> boundRawType = getOptionalRawType(bound);
+ if (boundRawType.isPresent() && !Object.class.equals(boundRawType.get())) {
+ if (!specializedClass.isPresent() || specializedClass.get().isAssignableFrom(boundRawType.get())) {
+ specializedClass = Optional.of(boundRawType.get());
+ }
+ }
+ }
+ return specializedClass;
+ }
}
return Optional.empty();
}
@@ -152,6 +166,11 @@
*/
static Type resolveItemVariableType(RuntimeTypeInfo item, TypeVariable<?> typeVariable, boolean warn) {
if (item == null) {
+ Optional<Class<?>> optionalRawType = getOptionalRawType(typeVariable);
+ if (optionalRawType.isPresent()) {
+ return optionalRawType.get();
+ }
+
//Bound not found, treat it as an Object.class
if (warn) {
LOGGER.warning(Messages.getMessage(MessageKeys.GENERIC_BOUND_NOT_FOUND,
diff --git a/src/main/java/org/eclipse/yasson/internal/VariableTypeInheritanceSearch.java b/src/main/java/org/eclipse/yasson/internal/VariableTypeInheritanceSearch.java
index 422ce78..0b4e28f 100644
--- a/src/main/java/org/eclipse/yasson/internal/VariableTypeInheritanceSearch.java
+++ b/src/main/java/org/eclipse/yasson/internal/VariableTypeInheritanceSearch.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -96,7 +96,7 @@
private Type searchRuntimeTypeArgument(ParameterizedType runtimeType, TypeVariable<?> typeVar) {
if (ReflectionUtils.getRawType(runtimeType) != typeVar.getGenericDeclaration()) {
- return null;
+ return ReflectionUtils.getOptionalRawType(typeVar).filter(rawType -> !Object.class.equals(rawType)).orElse(null);
}
TypeVariable[] bounds = typeVar.getGenericDeclaration().getTypeParameters();
for (int i = 0; i < bounds.length; i++) {
diff --git a/src/test/java/org/eclipse/yasson/serializers/SerializersTest.java b/src/test/java/org/eclipse/yasson/serializers/SerializersTest.java
index b35f184..2093e87 100644
--- a/src/test/java/org/eclipse/yasson/serializers/SerializersTest.java
+++ b/src/test/java/org/eclipse/yasson/serializers/SerializersTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -21,13 +21,18 @@
import static org.junit.jupiter.api.Assertions.fail;
import java.io.StringReader;
+import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Calendar;
+import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.SortedMap;
import java.util.TimeZone;
import java.util.TreeMap;
@@ -69,6 +74,8 @@
import org.eclipse.yasson.serializers.model.SimpleContainer;
import org.eclipse.yasson.serializers.model.StringWrapper;
import org.eclipse.yasson.serializers.model.SupertypeSerializerPojo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
/**
@@ -596,5 +603,93 @@
assertEquals("\"two\"", jsonb.toJson(new OneTwo()));
assertEquals("\"two\"", jsonb.toJson(new OneTwoThree()));
}
+
+ public class GenericBean<T> {
+
+ public T value;
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof GenericBean){
+ return Objects.equals(GenericBean.class.cast(obj).value, this.value);
+ }
+ return Boolean.FALSE;
+ }
+
+ }
+
+ public class GenericBeanSerializer implements JsonbSerializer<GenericBean> {
+
+ private Boolean called = Boolean.FALSE;
+
+ @Override
+ public void serialize(GenericBean t, JsonGenerator jg, SerializationContext sc) {
+ called = Boolean.TRUE;
+ jg.writeStartObject();
+ sc.serialize("value", t.value, jg);
+ jg.writeEnd();
+ }
+ }
+
+ public class GenericBeanDeserializer implements JsonbDeserializer<GenericBean> {
+
+ private Boolean called = Boolean.FALSE;
+
+ @Override
+ public GenericBean deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {
+ called = Boolean.TRUE;
+ JsonObject json = parser.getObject();
+ GenericBean<String> bean = new GenericBean<>();
+ bean.value = json.getString("value");
+ return bean;
+ }
+ }
+
+ @Test
+ public void testCustomDeserializerWithParameterizedType() {
+
+ GenericBeanSerializer genericBeanSerializer = new GenericBeanSerializer();
+ GenericBeanDeserializer genericBeanDeserializer = new GenericBeanDeserializer();
+
+ Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withDeserializers(genericBeanDeserializer).withSerializers(genericBeanSerializer));
+
+ GenericBean<String> bean1 = new GenericBean<>();
+ bean1.value = "test1";
+ GenericBean<String> bean2 = new GenericBean<>();
+ bean2.value = "test2";
+ GenericBean<String> bean3 = new GenericBean<>();
+ bean3.value = "test3";
+
+ Collection<GenericBean<String>> asList = Arrays.asList(bean1, bean2, bean3);
+
+ String toJson = jsonb.toJson(asList);
+
+ assertEquals(toJson, "[{\"value\":\"test1\"},{\"value\":\"test2\"},{\"value\":\"test3\"}]");
+ assertTrue(genericBeanSerializer.called);
+
+ List<GenericBean<String>> fromJson = jsonb.fromJson(
+ toJson,
+ new ParameterizedType() {
+ @Override
+ public Type[] getActualTypeArguments() {
+ return new Type[]{GenericBean.class};
+ }
+
+ @Override
+ public Type getRawType() {
+ return Collection.class;
+ }
+
+ @Override
+ public Type getOwnerType() {
+ return null;
+ }
+ }
+ );
+
+ assertEquals(asList, fromJson);
+ assertTrue(genericBeanDeserializer.called);
+
+ }
}