Crear conversores de tipos en Spring
En esta entrada vamos a ver las maneras en las que podemos crear conversores de tipos en Spring. Aunque Spring ofrece diferentes tipos de conversiones en algunas ocasiones vamos a necesitar crear algún conversor «custom» para una finalidad concreta, es en ese momento cuando haremos uso del API de tipos de Spring para crear nuestro propios conversor.
¿Cómo funciona la Conversión de tipos en Spring?
La conversión de tipos en Spring dependen de la clase ConversionService, la cual es una clase stateless que nos va a permitir convertir diferentes objetos.
Spring nos proporciona un API a través de ConversionService para ejecutar conversiones de tipo en tiempo de ejecución.
La interfaz que nos proporciona ConversionService nos da dos métodos a través de los cuales podremos convertir prácticamente cualquier tipo de uno a otro:
public interface ConversionService { boolean canConvert(Class<?> sourceType, Class<?> targetType); <T> T convert(Object source, Class<T> targetType); }
Crear un Conversor en Spring
Para crear un conversor en Spring necesitamos realizar dos pasos:
- Implementar la Clase Converter indicando el tipo de entrada y de salida.
- Registrar en FormatterRegistry la conversión creada.
Implementar Clase Converter para crear un Conversor de tipos en Spring
Lo primero que vamos a hacer es crear nuestra clase conversora implementando de la Clase Converter.
Por ejemplo vamos a partir de un String para convertirlo en nuestra clase Car.
@Getter @Setter public class Car{ private long id; private double price; private String brand; private String color; }
public class CarConverter implements Converter<String, Car> { @Override public Car convert(String car) { String[] carInfo = car.split(";"); return new Car( Long.parseLong(carInfo [0]), Double.parseDouble(carInfo [1]), carInfo[2], carInfo[3]); } }
Registrar en FormatterRegistry la conversión
Una vez hemos creado nuestra clase que se encarge de la conversión de un tipo a otro, es necesario registrarla. Para registrar un conversos en Spring Boot tenemos que hacer lo siguiente:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new CarConverter ()); } }
En donde haciendo uso de WebMvcConfigurer registramos nuestro conversor.
Ahora que tenemos nuestro conversor registrado podemos hacer un test apoyándonos en la clase ConversionService con la que podemos verificar nuestro conversor:
@Autowired ConversionService conversionService; @Test public void given_car_string_when_convert_to_object_the_return_car() { Car car = conversionService .convert("1;12500.99;Ford;yellow", Car.class); Car carExpected= new Car(1, 12500.99, "Ford", "yellow"); assertThat(car).isEqualToComparingFieldByField(carExpected); }
Implementación de conversores genéricos
Otra uso que podemos obtener del API de ConversionService es la creación de conversores de tipos genéricos haciendo uso de GenericConverter. Implementando la interfaz de GenericConverter vamos a poder realizar la definición de tipos genéricos, por ejemplo, si necesitamos convertir de String a LocalDate y de LocalDateTime a LocalDate podemos hacer uso de los genéricos.
El primer paso es definir los tipos de entrada que vamos a tener y como lo vamos a convertir:
public class DatesConverter implements GenericConverter { @Override public Set<ConvertiblePair> getConvertibleTypes () { ConvertiblePair[] pairs = new ConvertiblePair[] { new ConvertiblePair(String.class, LocalDate.class), new ConvertiblePair(LocalDateTime.class, LocalDate.class)}; return ImmutableSet.copyOf(pairs); } @Override public Object convert (Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (sourceType.getType() == BigDecimal.class) { return source; } if(sourceType.getType() == String.class) { String number = (String) source; return new BigDecimal(number); } else { Number number = (Number) source; BigDecimal converted = new BigDecimal(number.doubleValue()); return converted.setScale(2, BigDecimal.ROUND_HALF_EVEN); } } }
Al igual que hemos hecho al crear un conversor simple es necesario registrarlo:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new DatesConverter ()); } }
Factoría de conversores haciendo uso de ConversorService
Otra de las posibilidades que nos da el API de ConversionFactory es el uso de Factorías. Para ello nos permite hacer uso de la clase ConverterFactory. Esta funcionalidad es de gran ayuda para crear conversores para enum y poder convertir cualquier String a enum.
Por ejemplo si tenemos el siguiente enum:
enum Month{ JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER; }
Vamos a crear una factoría que sería capaz de crear a partir de cualquier enum a su String.
@Component public class FromStringToEnumConverterFactory implements ConverterFactory<String, Enum> { public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) { return new StringToEnumConverter(targetType); } private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> { private Class<T> enumType; public StringToEnumConverter(Class<T> enumType) { this.enumType = enumType; } public T convert(String source) { return (T) Enum.valueOf(this.enumType, source.trim()); } } }
Una vez hemos creado nuestra factoría conversora vamos a registrarla tal cual hemos hecho en los puntos anteriores.
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new FromStringToEnumConverterFactory ()); } }
Con nuestro conversor registrado vamos a crear un test para asegurar su funcionamiento, en este caso vamos a hacer uso de Parametized Test de Junit 5 a través de @MethodSource para añadir valores y verificar todo el enum.
@ParameterizedTest @MethodSource("months") public void given_months_when_converter_from_string_then_success(String month) { assertTrue(months.contains(conversionService.convert(month, Month.class))); } private static Stream<Arguments> months() { return Stream.of( arguments( "JANUARY"), arguments( "FEBRUARY"), arguments( "MARCH"), arguments( "APRIL"), arguments( "MAY"), arguments( "JUNE"), arguments( "JULY"), arguments( "AUGUST"), arguments( "SEPTEMBER"), arguments( "OCTOBER"), arguments( "NOVEMBER"), arguments( "DECEMBER") ); } public static final List<Month> months = Arrays.asList(Month.JANUARY,Month.FEBRUARY,Month.MARCH,Month.APRIL,Month.MAY, Month.JUNE,Month.JULY,Month.AUGUST,Month.SEPTEMBER, Month.OCTOBER, Month.NOVEMBER, Month.DECEMBER);
En el test anterior hemos verificado a través de Parametized Test como todos los enum de nuestra clase Month son convertidos de String a enum a través del conversor que hemos creado.
Conclusión
En esta entrada hemos visto las diferente formas para poder crear conversores de tipos en Spring, lo cual nos ayudará en simplificar nuestras aplicaciones evitando que realizar las conversiones de manera manual.
Si necesitas más información puedes escribirnos un comentario o un correo electrónico a refactorizando.web@gmail.com o también nos puedes contactar por nuestras redes sociales Facebook o twitter y te ayudaremos encantados!
Hola Noel,
Gracias por el artículo. Tengo una duda.
Aquí:
Car car = conversionService
.convert(«1;12500.99;Ford;yellow», Employee.class);
No debería ser:
Car car = conversionService
.convert(«1;12500.99;Ford;yellow», Car.class);
?
cierto! buen apunte. Gracias