spring-projects / spring-data-relational

Spring Data Relational. Home of Spring Data JDBC and Spring Data R2DBC.
https://spring.io/projects/spring-data-jdbc
Apache License 2.0
737 stars 339 forks source link

Make Converter accept TypeDescriptor #1820

Closed DreamStar92 closed 3 weeks ago

DreamStar92 commented 3 weeks ago

@schauder The current converters are unable to handle universal types, such as: IntegerToEnumReadConverter, JsonReadConverter.

expected a universal converter

    public interface Converter<S, T> {

        @Nullable
        T convert(S source, TypeDescriptor sourceTypeDescriptor);

    }

    public class JsonReadConverter implements Converter<String, JsonMapping> {

        private JsonMapper jsonMapper;

        @Override
        public JsonMapping convert(String source, TypeDescriptor sourceTypeDescriptor) {
            try {
//                current converter not support container class, so not use TypeReference
//                return jsonMapper.readValue(source, new TypeReference<JsonMapping>(){
//                    @Override
//                    public Type getType() {
//                        return sourceTypeDescriptor.getResolvableType().getType();
//                    }
//                });
                return (JsonMapping) jsonMapper.readValue(source, sourceTypeDescriptor.getResolvableType().getRawClass());
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }

    }

    public class EnumReadConverter implements Converter<Integer, EnumMapping> {

        @Override
        @SuppressWarnings("unchecked")
        public EnumMapping convert(Integer source, TypeDescriptor sourceTypeDescriptor) {
            try {
                return EnumMapping.indexOf((Class<? extends EnumMapping>) sourceTypeDescriptor.getResolvableType().getRawClass(), source);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

    }

now each type needs to declare a converter

@Component("inventoryConverters")
public class Converters implements ConverterRegistrar {
    @Override
    public List<?> converters(JsonMapper jsonMapper) {
        return new ArrayList<Object>() {{
            add(new EnumReadConverter<Category>(Category.class) {
            });
            add(new JsonReadConverter<AvailableWarehouseTypes>(AvailableWarehouseTypes.class, jsonMapper) {
            });
            add(new EnumReadConverter<SkuUnit>(SkuUnit.class) {
            });
            add(new JsonReadConverter<SkuUnitItems>(SkuUnitItems.class, jsonMapper) {
            });
            add(new EnumReadConverter<WarehouseType>(WarehouseType.class) {
            });
        }};
    }
}

Anticipated modifications Interface adjustment and related calls in data-jdbc

@FunctionalInterface
public interface Converter<S, T> {

    @Nullable
        @Deprecated
    default T convert(S source){
        };

        @Nullable
        default T convert(S source, TypeDescriptor sourceTypeDescriptor) {
            convert(source);
        }

    default <U> Converter<S, U> andThen(Converter<? super T, ? extends U> after) {
        Assert.notNull(after, "After Converter must not be null");
        return (S s) -> {
            T initialResult = convert(s);
            return (initialResult != null ? after.convert(initialResult) : null);
        };
    }

}
mp911de commented 3 weeks ago

That's not going to work as our CustomConversions infrastructure is working on Class as type. Any generics aren't available on that level. Types with generic types are only available when working with properties. Because you would register a converter, we resort to Class and reduce the available information.

Going forward, Converter<S, T> is a Spring Framework type that we do not control. You could implement GenericConverter by providing getConvertibleTypes().

Taking a step back, enums are handled in private getPotentiallyConvertedSimpleWrite and getPotentiallyConvertedSimpleRead methods. We could make getPotentiallyConvertedSimpleWrite additionally a protected method that you could then override and introduce your own handling.

Other than that, we cannot change a type that resides in Spring Framework.

DreamStar92 commented 3 weeks ago

Effectively solved my problem.

Hopefully GenericConverter can be mentioned in the Mapping.Converter section of the documentation.