Skip to content

Commit

Permalink
fix custom deserializer not working with generic types (#480)
Browse files Browse the repository at this point in the history
support for generic type and type variable deserialization with custom deserializer

Signed-off-by: Alessandro Moscatelli <alessandro.moscatelli@visiontech.cloud>
  • Loading branch information
amoscatelli committed Mar 12, 2021
1 parent f8263f7 commit d5a4985
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 9 deletions.
10 changes: 5 additions & 5 deletions src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -203,10 +203,10 @@ private <T extends AbstractComponentBinding> Optional<T> searchComponentBinding(
}
}

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);
Expand All @@ -217,7 +217,7 @@ private <T extends AbstractComponentBinding> Optional<T> searchComponentBinding(
}

// 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()) {
Expand Down
21 changes: 20 additions & 1 deletion src/main/java/org/eclipse/yasson/internal/ReflectionUtils.java
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -62,6 +62,20 @@ public static Optional<Class<?>> getOptionalRawType(Type type) {
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();
}
Expand Down Expand Up @@ -152,6 +166,11 @@ public static Optional<Type> resolveOptionalType(RuntimeTypeInfo info, Type type
*/
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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -96,7 +96,7 @@ private Type checkSubclassRuntimeInfo(TypeVariable typeVar) {

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++) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -55,6 +60,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;

import jakarta.json.Json;
Expand Down Expand Up @@ -596,5 +603,93 @@ public void testSerializerMatchingInterfaces02() {
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);

}

}

0 comments on commit d5a4985

Please sign in to comment.