4.5 Custom Type Converters

Micronaut features a built in type conversion mechanism that is extensible. To add additional type converters you register beans of type TypeConverter.

The following example shows how to use one of the built-in converters (Map to an Object) or create your own.

Consider the following ConfigurationProperties:

  1. @ConfigurationProperties(MyConfigurationProperties.PREFIX)
  2. public class MyConfigurationProperties {
  3. public LocalDate getUpdatedAt() {
  4. return this.updatedAt;
  5. }
  6. public static final String PREFIX = "myapp";
  7. protected LocalDate updatedAt;
  8. }
  1. @ConfigurationProperties(MyConfigurationProperties.PREFIX)
  2. class MyConfigurationProperties {
  3. public static final String PREFIX = "myapp"
  4. protected LocalDate updatedAt
  5. LocalDate getUpdatedAt() {
  6. return this.updatedAt
  7. }
  8. }
  1. @ConfigurationProperties(MyConfigurationProperties.PREFIX)
  2. class MyConfigurationProperties {
  3. var updatedAt: LocalDate? = null
  4. protected set
  5. companion object {
  6. const val PREFIX = "myapp"
  7. }
  8. }

The type MyConfigurationProperties features a property called updatedAt which is of type LocalDate.

Now let’s say you want to allow binding to this property from a map via configuration:

  1. private static ApplicationContext ctx;
  2. @BeforeClass
  3. public static void setupCtx() {
  4. ctx = ApplicationContext.run(
  5. new LinkedHashMap<String, Object>() {{
  6. put("myapp.updatedAt", (1)
  7. new LinkedHashMap<String, Integer>() {{
  8. put("day", 28);
  9. put("month", 10);
  10. put("year", 1982);
  11. }}
  12. );
  13. }}
  14. );
  15. }
  16. @AfterClass
  17. public static void teardownCtx() {
  18. if(ctx != null) {
  19. ctx.stop();
  20. }
  21. }
  1. @AutoCleanup
  2. @Shared
  3. ApplicationContext ctx = ApplicationContext.run(
  4. "myapp.updatedAt": [day: 28, month: 10, year: 1982] (1)
  5. )
  1. lateinit var ctx: ApplicationContext
  2. @BeforeEach
  3. fun setup() {
  4. ctx = ApplicationContext.run(
  5. mapOf(
  6. "myapp.updatedAt" to mapOf( (1)
  7. "day" to 28,
  8. "month" to 10,
  9. "year" to 1982
  10. )
  11. )
  12. )
  13. }
  14. @AfterEach
  15. fun teardown() {
  16. ctx?.close()
  17. }
1Note how we match the myapp prefix and updatedAt property name in our MyConfigurationProperties class above

This won’t work by default, since there is no built in conversion from Map to LocalDate. To resolve this you can define a custom TypeConverter:

  1. import io.micronaut.core.convert.ConversionContext;
  2. import io.micronaut.core.convert.ConversionService;
  3. import io.micronaut.core.convert.TypeConverter;
  4. import javax.inject.Singleton;
  5. import java.time.DateTimeException;
  6. import java.time.LocalDate;
  7. import java.util.Map;
  8. import java.util.Optional;
  9. @Singleton
  10. public class MapToLocalDateConverter implements TypeConverter<Map, LocalDate> { (1)
  11. @Override
  12. public Optional<LocalDate> convert(Map propertyMap, Class<LocalDate> targetType, ConversionContext context) {
  13. Optional<Integer> day = ConversionService.SHARED.convert(propertyMap.get("day"), Integer.class);
  14. Optional<Integer> month = ConversionService.SHARED.convert(propertyMap.get("month"), Integer.class);
  15. Optional<Integer> year = ConversionService.SHARED.convert(propertyMap.get("year"), Integer.class);
  16. if (day.isPresent() && month.isPresent() && year.isPresent()) {
  17. try {
  18. return Optional.of(LocalDate.of(year.get(), month.get(), day.get())); (2)
  19. } catch (DateTimeException e) {
  20. context.reject(propertyMap, e); (3)
  21. return Optional.empty();
  22. }
  23. }
  24. return Optional.empty();
  25. }
  26. }
  1. import io.micronaut.core.convert.ConversionContext
  2. import io.micronaut.core.convert.ConversionService
  3. import io.micronaut.core.convert.TypeConverter
  4. import javax.inject.Singleton
  5. import java.time.DateTimeException
  6. import java.time.LocalDate
  7. @Singleton
  8. class MapToLocalDateConverter implements TypeConverter<Map, LocalDate> { (1)
  9. @Override
  10. Optional<LocalDate> convert(Map propertyMap, Class<LocalDate> targetType, ConversionContext context) {
  11. Optional<Integer> day = ConversionService.SHARED.convert(propertyMap.get("day"), Integer.class)
  12. Optional<Integer> month = ConversionService.SHARED.convert(propertyMap.get("month"), Integer.class)
  13. Optional<Integer> year = ConversionService.SHARED.convert(propertyMap.get("year"), Integer.class)
  14. if (day.isPresent() && month.isPresent() && year.isPresent()) {
  15. try {
  16. return Optional.of(LocalDate.of(year.get(), month.get(), day.get())) (2)
  17. } catch (DateTimeException e) {
  18. context.reject(propertyMap, e) (3)
  19. return Optional.empty()
  20. }
  21. }
  22. return Optional.empty()
  23. }
  24. }
  1. import io.micronaut.core.convert.ConversionContext
  2. import io.micronaut.core.convert.ConversionService
  3. import io.micronaut.core.convert.TypeConverter
  4. import javax.inject.Singleton
  5. import java.time.DateTimeException
  6. import java.time.LocalDate
  7. import java.util.Optional
  8. @Singleton
  9. class MapToLocalDateConverter : TypeConverter<Map<*, *>, LocalDate> { (1)
  10. override fun convert(propertyMap: Map<*, *>, targetType: Class<LocalDate>, context: ConversionContext): Optional<LocalDate> {
  11. val day = ConversionService.SHARED.convert(propertyMap["day"], Int::class.java)
  12. val month = ConversionService.SHARED.convert(propertyMap["month"], Int::class.java)
  13. val year = ConversionService.SHARED.convert(propertyMap["year"], Int::class.java)
  14. if (day.isPresent && month.isPresent && year.isPresent) {
  15. try {
  16. return Optional.of(LocalDate.of(year.get(), month.get(), day.get())) (2)
  17. } catch (e: DateTimeException) {
  18. context.reject(propertyMap, e) (3)
  19. return Optional.empty()
  20. }
  21. }
  22. return Optional.empty()
  23. }
  24. }
1The class implements TypeConverter which takes two generic arguments. The type you are converting from and the type you are converting to
2The implementation delegate to the default shared conversion service to convert the parts of the map that make the day, month and year into a LocalDate
3If an exception occurs you can call reject(..) which propagates additional information to the container if something goes wrong during binding