Ktor 2.3.12 Help

Data conversion

The DataConversion plugin allows you to serialize and deserialize a list of values. By default, Ktor handles primitive types and enums through the DefaultConversionService. You can extend this service to handle additional types by installing and configuring the DataConversion plugin.

If you are using the Locations plugin and want to support custom types in the Location class parameters, you can utilize the DataConversion plugin to handle those types.

Add dependencies

To use DataConversion, you need to include the ktor-server-data-conversion artifact in the build script:

implementation("io.ktor:ktor-server-data-conversion:$ktor_version")
implementation "io.ktor:ktor-server-data-conversion:$ktor_version"
<dependency> <groupId>io.ktor</groupId> <artifactId>ktor-server-data-conversion-jvm</artifactId> <version>${ktor_version}</version> </dependency>

Install DataConversion

To install the DataConversion plugin to the application, pass it to the install function in the specified module. The code snippets below show how to install DataConversion...

  • ... inside the embeddedServer function call.

  • ... inside the explicitly defined module, which is an extension function of the Application class.

import io.ktor.server.engine.* import io.ktor.server.netty.* import io.ktor.server.application.* import io.ktor.server.plugins.dataconversion.* fun main() { embeddedServer(Netty, port = 8080) { install(DataConversion) // ... }.start(wait = true) }
import io.ktor.server.application.* import io.ktor.server.plugins.dataconversion.* // ... fun Application.module() { install(DataConversion) // ... }

Add converters

You can define type conversions within the DataConversion configuration. Provide a convert<T> method for the specified type and use the available functions to serialize and deserialize a list of values:

  • Use the decode() function to deserialize a list of values. It takes a list of strings, representing repeated values in the URL and returns the decoded value.

    decode { values -> // converter: (values: List<String>) -> Any? //deserialize values }
  • Use the encode() function to serialize a value. The function takes an arbitrary value and returns a list of strings representing it.

    encode { value -> // converter: (value: Any?) -> List<String> //serialize value }

Access the service

You can access the DataConversion service from the current context:

val dataConversion = application.conversionService

You can then use the converter service to call the callback functions:

  • The fromValues(values: List<String>, type: TypeInfo) callback function accepts values as a list of strings, and the TypeInfo to convert the value to and returns the decoded value.

  • The toValues(value: Any?) callback function accepts an arbitrary value and returns a list of strings representing it.

Example

In the following example, a converter for the type LocalDate is defined and configured to serialize and deserialize values. When the encode function is called, the service will convert the value using a SimpleDateFormat and return a list containing the formatted value. When the decode function is called, the service will format the date as a LocalDate and return it.

install(DataConversion) { convert<LocalDate> { // this: DelegatingConversionService val formatter = DateTimeFormatterBuilder() .appendValue(ChronoField.YEAR, 4, 4, SignStyle.NEVER) .appendValue(ChronoField.MONTH_OF_YEAR, 2) .appendValue(ChronoField.DAY_OF_MONTH, 2) .toFormatter(Locale.ROOT) decode { values -> // converter: (values: List<String>) -> Any? LocalDate.from(formatter.parse(values.single())) } encode { value -> // converter: (value: Any?) -> List<String> listOf(SimpleDateFormat.getInstance().format(value)) } } }

The conversion service can then be called manually to retrieve the encoded and decoded values:

val encodedDate = application.conversionService.toValues(call.parameters["date"]) val decodedDate = application.conversionService.fromValues(encodedDate, typeInfo<LocalDate>())

Customize enum serialization with Locations

Another potential use case is to customize how a specific enum is serialized. By default, enums are serialized and deserialized using their name attribute in a case-sensitive fashion. But you can, for example, serialize them as lower case and deserialize them as case-insensitive, as seen in the following example:

enum class ArticleEnum { A, B, C } @Location("/") class LocationWithEnum(val article: ArticleEnum) fun Application.module() { install(Locations) install(DataConversion) { convert<ArticleEnum> { decode { values -> ArticleEnum.entries.first { it.name.lowercase() in values } } encode { value -> listOf(value.name.lowercase()) } } } routing { get<LocationWithEnum> { data -> call.respondText("The received article is ${data.article.name}") } }

For the full example, see data-conversion

Last modified: 02 April 2024