Description
If there are multiple converters for similar types, but with different generic type arguments that are partially unresolvable - then the generic type arguments are effectively ignored and as a result one might get a non-matching converter.
The issue can be demonstrated with this test:
class GenericConversionServiceTests {
private final GenericConversionService conversionService = new GenericConversionService();
@Test
void stringListToListOfSubClassesOfUnboundGenericClass() throws Exception {
conversionService.addConverter(new StringListToAListConverter());
conversionService.addConverter(new StringListToBListConverter());
List<ARaw> aList = (List<ARaw>) conversionService.convert(List.of("foo"),
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)),
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(ARaw.class)));
assertThat(aList).allMatch(e -> e instanceof ARaw);
List<BRaw> bList = (List<BRaw>) conversionService.convert(List.of("foo"),
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)),
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(BRaw.class)));
assertThat(bList).allMatch(e -> e instanceof BRaw);
}
public class ARaw extends GenericBaseClass {
}
public class BRaw extends GenericBaseClass {
}
public class GenericBaseClass<T> {
}
public class StringListToAListConverter implements Converter<List<String>, List<ARaw>> {
@Override
public List<ARaw> convert(List<String> source) {
return List.of(new ARaw());
}
}
public class StringListToBListConverter implements Converter<List<String>, List<BRaw>> {
@Override
public List<BRaw> convert(List<String> source) {
return List.of(new BRaw());
}
}
}
As far as I understand this could be fixed by a small change in GenericConversionService.ConverterAdapter#matches to take the resolvable part into account even if the type is not completely unresolvable. I.e. if the resolvable part does not match, then this converter does not match.
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
// Check raw type first...
if (this.typeInfo.getTargetType() != targetType.getObjectType()) {
return false;
}
// Full check for complex generic type match required?
ResolvableType rt = targetType.getResolvableType();
if (!(rt.getType() instanceof Class) && !rt.isAssignableFrom(this.targetType) &&
- !this.targetType.hasUnresolvableGenerics()) {
+ (!this.targetType.hasUnresolvableGenerics() || !rt.isAssignableFromResolvedPart(this.targetType))) {
return false;
}
return !(this.converter instanceof ConditionalConverter conditionalConverter) ||
conditionalConverter.matches(sourceType, targetType);
}
The concrete use case I have is converting a List<A>
to List<B>
where B
is a generated protobuf class which has partially unresolvable generics due to this. Also, I cannot use a plain Converter<A,B>
because the converter needs access to the full source list to produce the target list - so I the natural solution is to have a Converter<List<A>, List<B>>
, but it does not work since I have multiple such converters with same source type and different target types.
One possible workaround would be to wrap the Lists in distinct wrapper classes during conversion, but I think it would be better to just make GenericConversionService
consider partially resolvable generics.