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:
@ConfigurationProperties(MyConfigurationProperties.PREFIX)
public class MyConfigurationProperties {
public LocalDate getUpdatedAt() {
return this.updatedAt;
}
public static final String PREFIX = "myapp";
protected LocalDate updatedAt;
}
@ConfigurationProperties(MyConfigurationProperties.PREFIX)
class MyConfigurationProperties {
public static final String PREFIX = "myapp"
protected LocalDate updatedAt
LocalDate getUpdatedAt() {
return this.updatedAt
}
}
@ConfigurationProperties(MyConfigurationProperties.PREFIX)
class MyConfigurationProperties {
var updatedAt: LocalDate? = null
protected set
companion object {
const val PREFIX = "myapp"
}
}
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:
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 you can define a custom TypeConverter:
import io.micronaut.core.convert.ConversionContext;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.convert.TypeConverter;
import javax.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 javax.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.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 javax.inject.Singleton
import java.time.DateTimeException
import java.time.LocalDate
import java.util.Optional
@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 takes two generic arguments. The type you are converting from and the type you are converting to |
2 | The 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 |
3 | If an exception occurs you can call reject(..) which propagates additional information to the container if something goes wrong during binding |