4.5 Custom Type Converters
Micronaut includes an extensible type conversion mechanism. 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:
@ConfigurationProperties(MyConfigurationProperties.PREFIX)
public class MyConfigurationProperties {
public static final String PREFIX = "myapp";
protected LocalDate updatedAt;
public LocalDate getUpdatedAt() {
return updatedAt;
}
}
@ConfigurationProperties(MyConfigurationProperties.PREFIX)
class MyConfigurationProperties {
public static final String PREFIX = "myapp"
protected LocalDate updatedAt
LocalDate getUpdatedAt() {
updatedAt
}
}
@ConfigurationProperties(MyConfigurationProperties.PREFIX)
class MyConfigurationProperties {
var updatedAt: LocalDate? = null
protected set
companion object {
const val PREFIX = "myapp"
}
}
The type MyConfigurationProperties
has a property named updatedAt
of type LocalDate.
To bind this property from a map via configuration:
private static ApplicationContext ctx;
@BeforeClass
public static void setupCtx() {
ctx = ApplicationContext.run(
new LinkedHashMap<String, Object>() {{
put("myapp.updatedAt", (1)
new LinkedHashMap<String, Integer>() {{
put("day", 28);
put("month", 10);
put("year", 1982);
}}
);
}}
);
}
@AfterClass
public static void teardownCtx() {
if(ctx != null) {
ctx.stop();
}
}
@AutoCleanup
@Shared
ApplicationContext ctx = ApplicationContext.run(
"myapp.updatedAt": [day: 28, month: 10, year: 1982] (1)
)
lateinit var ctx: ApplicationContext
@BeforeEach
fun setup() {
ctx = ApplicationContext.run(
mapOf(
"myapp.updatedAt" to mapOf( (1)
"day" to 28,
"month" to 10,
"year" to 1982
)
)
)
}
@AfterEach
fun teardown() {
ctx?.close()
}
1 | Note 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, define a custom TypeConverter:
import io.micronaut.core.convert.ConversionContext;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.convert.TypeConverter;
import jakarta.inject.Singleton;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.util.Map;
import java.util.Optional;
@Singleton
public class MapToLocalDateConverter implements TypeConverter<Map, LocalDate> { (1)
@Override
public Optional<LocalDate> convert(Map propertyMap, Class<LocalDate> targetType, ConversionContext context) {
Optional<Integer> day = ConversionService.SHARED.convert(propertyMap.get("day"), Integer.class);
Optional<Integer> month = ConversionService.SHARED.convert(propertyMap.get("month"), Integer.class);
Optional<Integer> year = ConversionService.SHARED.convert(propertyMap.get("year"), Integer.class);
if (day.isPresent() && month.isPresent() && year.isPresent()) {
try {
return Optional.of(LocalDate.of(year.get(), month.get(), day.get())); (2)
} catch (DateTimeException e) {
context.reject(propertyMap, e); (3)
return Optional.empty();
}
}
return Optional.empty();
}
}
import io.micronaut.core.convert.ConversionContext
import io.micronaut.core.convert.ConversionService
import io.micronaut.core.convert.TypeConverter
import jakarta.inject.Singleton
import java.time.DateTimeException
import java.time.LocalDate
@Singleton
class MapToLocalDateConverter implements TypeConverter<Map, LocalDate> { (1)
@Override
Optional<LocalDate> convert(Map propertyMap, Class<LocalDate> targetType, ConversionContext context) {
Optional<Integer> day = ConversionService.SHARED.convert(propertyMap.day, Integer)
Optional<Integer> month = ConversionService.SHARED.convert(propertyMap.month, Integer)
Optional<Integer> year = ConversionService.SHARED.convert(propertyMap.year, Integer)
if (day.present && month.present && year.present) {
try {
return Optional.of(LocalDate.of(year.get(), month.get(), day.get())) (2)
} catch (DateTimeException e) {
context.reject(propertyMap, e) (3)
return Optional.empty()
}
}
return Optional.empty()
}
}
import io.micronaut.core.convert.ConversionContext
import io.micronaut.core.convert.ConversionService
import io.micronaut.core.convert.TypeConverter
import java.time.DateTimeException
import java.time.LocalDate
import java.util.Optional
import jakarta.inject.Singleton
@Singleton
class MapToLocalDateConverter : TypeConverter<Map<*, *>, LocalDate> { (1)
override fun convert(propertyMap: Map<*, *>, targetType: Class<LocalDate>, context: ConversionContext): Optional<LocalDate> {
val day = ConversionService.SHARED.convert(propertyMap["day"], Int::class.java)
val month = ConversionService.SHARED.convert(propertyMap["month"], Int::class.java)
val year = ConversionService.SHARED.convert(propertyMap["year"], Int::class.java)
if (day.isPresent && month.isPresent && year.isPresent) {
try {
return Optional.of(LocalDate.of(year.get(), month.get(), day.get())) (2)
} catch (e: DateTimeException) {
context.reject(propertyMap, e) (3)
return Optional.empty()
}
}
return Optional.empty()
}
}
1 | The class implements TypeConverter which has two generic arguments, the type you are converting from, and the type you are converting to |
2 | The implementation delegates to the default shared conversion service to convert the values from the Map used to create a LocalDate |
3 | If an exception occurs during binding, call reject(..) which propagates additional information to the container |