I got sick and tired of type-related errors and decided to write a type conversion service It's not particularly pretty what with those casts and suppressions, but it seems to do what it was designed for A few things to note: 1. I didn't write javadocs: those are to be written in a non-English language. The most important thing to know is `S` means "source type", `T` means "target type" 2. In case you're wondering, I had to introduce the `S` generic type parameter in `Converter` solely for this to work: ```java @Override @SuppressWarnings("unchecked") public <S> Optional<T> convert(S value) { Class<?> valueType = value.getClass(); // I can't write <? super valueType> Function<? super S, Optional<T>> mapping = (Function<? super S, Optional<T>>) getMappingForType(valueType); // if it was an unbounded wildcard, this line wouldn't compile Optional<T> optionalValue = mapping.apply(value); return optionalValue; } ``` 3. I realize I don't account for overflows and `NumberFormatExeption`s. If truth be told, I couldn't figure out a good way to handle that *(should `ConverterService` catch? should it be the responsibility of individual `Converter`s? how do I make it not ugly? try-catches are ugly as is, let alone in lambdas)* Let it throw, for the time being (or forever) How can I improve it? I was hoping to make it cleaner and more robust. **`Converter`:** ```java package solution.common.converter; import java.util.Optional; public interface Converter<T> { <S> Optional<T> convert(S value); } ``` **`Converter` implementation:** ```java package solution.common.converter; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.function.Function; public class BasicConverter<T> implements Converter<T> { private final Map<Class<?>, Function<?, Optional<T>>> mappings = new HashMap<>(); public <S> void addMapping(Class<S> sourceType, Function<S, Optional<T>> mappingFunction) { mappings.put(sourceType, mappingFunction); } @Override @SuppressWarnings("unchecked") public <S> Optional<T> convert(S value) { Class<?> valueType = value.getClass(); Function<? super S, Optional<T>> mapping = (Function<? super S, Optional<T>>) getMappingForType(valueType); Optional<T> optionalValue = mapping.apply(value); return optionalValue; } private Function<?, Optional<T>> getMappingForType(Class<?> type) { Function<?, Optional<T>> mapping = mappings.containsKey(type) ? mappings.get(type) : findSuperTypeMapping(type); return mapping; } @SuppressWarnings("unchecked") private <S> Function<? super S, Optional<T>> findSuperTypeMapping(Class<S> valueType) { for (Class<?> aClass : mappings.keySet()) { if (aClass.isAssignableFrom(valueType)) return (Function<? super S, Optional<T>>) mappings.get(aClass); } return noopMapping(); } private <S> Function<S, Optional<T>> noopMapping() { return input -> Optional.empty(); } } ``` **`ConverterService`:** ```java package solution.common.converter; import java.util.Optional; public interface ConverterService { <T> Optional<T> convert(Object value, Class<T> targetType); } ``` **`ConverterService` implementation:** ```java package solution.common.converter; import java.util.HashMap; import java.util.Map; import java.util.Optional; public class BasicConverterService implements ConverterService { private final Map<Class<?>, Converter<?>> converterMap = new HashMap<>(); public <T> void addConverter(Class<T> targetType, Converter<T> converter) { converterMap.put(targetType, converter); } @Override @SuppressWarnings("unchecked") public <T> Optional<T> convert(Object value, Class<T> targetType) { if (targetType.isAssignableFrom(value.getClass())) return (Optional<T>) Optional.of(value); Converter<?> converter = (converterMap.containsKey(targetType)) ? converterMap.get(targetType) : findSubtypeConverter(targetType); return (Optional<T>) converter.convert(value); } private Converter<?> findSubtypeConverter(Class<?> targetType) { for (Class<?> aClass : converterMap.keySet()) { if (targetType.isAssignableFrom(aClass)) return converterMap.get(aClass); } return noopConverter(); } private <T> Converter<T> noopConverter() { return new Converter<T>() { @Override public <S> Optional<T> convert(S value) { return Optional.empty(); } }; } } ``` **Tests:** ```java package solution.common.converter; import org.junit.jupiter.api.Test; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; class BasicConverterServiceTest { @Test void convert_ifNoConvertersAdded_andTargetTypeDoesNotMatchSourceType_returnsEmptyOptional() { BasicConverterService converterService = new BasicConverterService(); Optional<Integer> valueOptional = converterService.convert("42", Integer.class); assertFalse(valueOptional.isPresent()); } @Test void convert_ifNoConverterAdded_butTargetTypeMatchesSourceType_returnsOptionalOfInputValue() { BasicConverterService converterService = new BasicConverterService(); Optional<String> valueOptional = converterService.convert("42", String.class); assertTrue(valueOptional.isPresent()); assertEquals("42", valueOptional.get()); } @Test void convert_ifConverterMatchingTypeAdded_returnsOptionalOfThatConvertersResult() { BasicConverterService converterService = new BasicConverterService(); converterService.addConverter(Integer.class, new Converter<Integer>() { @Override public <S> Optional<Integer> convert(S value) { return Optional.of(Integer.parseInt((String) value)); } }); Optional<Integer> valueOptional = converterService.convert("42", Integer.class); assertTrue(valueOptional.isPresent()); assertEquals(42, valueOptional.get()); } @Test void convert_ifConverterMatchingSubtypeAdded_returnsOptionalOfThatConvertersResult() { BasicConverterService converterService = new BasicConverterService(); converterService.addConverter(Integer.class, new Converter<Integer>() { @Override public <S> Optional<Integer> convert(S value) { return Optional.of(Integer.parseInt((String) value)); } }); Optional<Number> valueOptional = converterService.convert("42", Number.class); assertTrue(valueOptional.isPresent()); assertEquals(42, valueOptional.get()); } } ``` Here's a factory that returns a more fully-featured `ConverterService`. It seems to cover most, if not all, of our project's needs. It contains one line that doesn't compile, the one that involves `DateUtils`, but I decided to omit that utility for the purpose of this code review. Basically, it checks if the input object is `Date` and casts if so; if it's a string, it tries a number of date formats to parse it, one by one ```java package solution.common.di; import solution.common.converter.BasicConverter; import solution.common.converter.BasicConverterService; import solution.common.converter.Converter; import solution.common.converter.ConverterService; import solution.common.date.DateUtils; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Date; import java.util.Optional; public class Converters { public static ConverterService converterService() { BasicConverterService converterService = new BasicConverterService(); converterService.addConverter(Date.class, toDateConverter()); converterService.addConverter(Byte.class, toByteConverter()); converterService.addConverter(Short.class, toShortConverter()); converterService.addConverter(Integer.class, toIntegerConverter()); converterService.addConverter(BigInteger.class, toBigIntegerConverter()); converterService.addConverter(Long.class, toLongConverter()); converterService.addConverter(Float.class, toFloatConverter()); converterService.addConverter(Double.class, toDoubleConverter()); converterService.addConverter(BigDecimal.class, toBigDecimalConverter()); converterService.addConverter(String.class, toStringConverter()); return converterService; } private static Converter<Date> toDateConverter() { BasicConverter<Date> converter = new BasicConverter<>(); converter.addMapping(Object.class, DateUtils::asDate); return converter; } private static Converter<Byte> toByteConverter() { BasicConverter<Byte> converter = new BasicConverter<>(); converter.addMapping(String.class, input -> Optional.of(input) .map(Byte::parseByte)); converter.addMapping(Number.class, input -> Optional.of(input).map(Number::byteValue)); return converter; } private static Converter<Short> toShortConverter() { BasicConverter<Short> converter = new BasicConverter<>(); converter.addMapping(String.class, input -> Optional.of(input) .map(Short::parseShort)); converter.addMapping(Number.class, input -> Optional.of(input).map(Number::shortValue)); return converter; } private static Converter<Integer> toIntegerConverter() { BasicConverter<Integer> converter = new BasicConverter<>(); converter.addMapping(String.class, input -> Optional.of(input) .map(Integer::parseInt)); converter.addMapping(Number.class, input -> Optional.of(input).map(Number::intValue)); return converter; } private static Converter<Long> toLongConverter() { BasicConverter<Long> converter = new BasicConverter<>(); converter.addMapping(String.class, input -> Optional.of(input) .map(Long::parseLong)); converter.addMapping(Number.class, input -> Optional.of(input).map(Number::longValue)); return converter; } private static Converter<BigInteger> toBigIntegerConverter() { BasicConverter<BigInteger> converter = new BasicConverter<>(); converter.addMapping(String.class, input -> Optional.of(input) .map(BigInteger::new)); converter.addMapping(Number.class, input -> Optional.of(input) .map(Number::longValue) .map(BigInteger::valueOf)); return converter; } private static Converter<Float> toFloatConverter() { BasicConverter<Float> converter = new BasicConverter<>(); converter.addMapping(String.class, input -> Optional.of(input) .map(Float::parseFloat)); converter.addMapping(Number.class, input -> Optional.of(input).map(Number::floatValue)); return converter; } private static Converter<Double> toDoubleConverter() { BasicConverter<Double> converter = new BasicConverter<>(); converter.addMapping(String.class, input -> Optional.of(input) .map(Double::parseDouble)); converter.addMapping(Number.class, input -> Optional.of(input).map(Number::doubleValue)); return converter; } private static Converter<BigDecimal> toBigDecimalConverter() { BasicConverter<BigDecimal> converter = new BasicConverter<>(); converter.addMapping(String.class, input -> Optional.of(input) .map(BigDecimal::new)); converter.addMapping(Number.class, input -> Optional.of(input) .map(Number::doubleValue) .map(BigDecimal::new)); return converter; } private static Converter<String> toStringConverter() { BasicConverter<String> converter = new BasicConverter<>(); converter.addMapping(Object.class, input -> Optional.of(input).map(Object::toString)); return converter; } } ```