Skip to content

Latest commit

 

History

History
119 lines (89 loc) · 4.18 KB

data-conversion.md

File metadata and controls

119 lines (89 loc) · 4.18 KB

Required dependencies: io.ktor:%artifact_name%

%plugin_name% is a plugin that allows to serialize and deserialize a list of values. By default, it handles primitive types and enums, but it can also be configured to handle additional types. If you are using the Locations plugin and want to support custom types as part of its parameters, you can add new custom converters with this service.

Add dependencies {id="add_dependencies"}

Install %plugin_name% {id="install_plugin"}

Add converters {id="add-converters"}

To configure %plugin_name%, provide a convert<T> method to define type conversions. Inside, you have to provide a decoder and an encoder with the decode and encode methods accepting callbacks.

  • decode callback: converter: (values: List<String>, type: Type) -> Any? Accepts values, a list of strings representing repeated values in the URL, for example, a=1&a=2, and accepts the type to convert to. It should return the decoded value.
  • encode callback: converter: (value: Any?) -> List<String> Accepts an arbitrary value, and should return a list of strings representing the value. When returning a list of a single element, it will be serialized as key=item1. For multiple values, it will be serialized in the query string as: samekey=item1&samekey=item2.

For example:

install(DataConversion) {
    convert<Date> { // this: DelegatingConversionService
        val format = SimpleDateFormat.getInstance()
    
        decode { values, _ -> // converter: (values: List<String>, type: Type) -> Any?
            values.singleOrNull()?.let { format.parse(it) }
        }

        encode { value -> // converter: (value: Any?) -> List<String>
            when (value) {
                null -> listOf()
                is Date -> listOf(SimpleDateFormat.getInstance().format(value))
                else -> throw DataConversionException("Cannot convert $value as Date")
            }
        }
    }
}

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

enum class LocationEnum {
    A, B, C
}

@Location("/") class LocationWithEnum(val e: LocationEnum)

@Test fun `location class with custom enum value`() = withLocationsApplication {
    application.install(DataConversion) {
        convert(LocationEnum::class) {
            encode { if (it == null) emptyList() else listOf((it as LocationEnum).name.toLowerCase()) }
            decode { values, type -> LocationEnum.values().first { it.name.toLowerCase() in values } }
        }
    }
    application.routing {
        get<LocationWithEnum> {
            call.respondText(call.locations.resolve<LocationWithEnum>(LocationWithEnum::class, call).e.name)
        }
    }

    urlShouldBeHandled("/?e=a", "A")
    urlShouldBeHandled("/?e=b", "B")
}

Accessing the service

{id="service"}

You can access the %plugin_name% service from any call:

val dataConversion = call.conversionService

The ConversionService interface

{id="interface"}

interface ConversionService {

  fun fromValues(values: List<String>, type: TypeInfo): Any?
  fun toValues(value: Any?): List<String>
}

{id="ConversionService"}

class DelegatingConversionService(private val klass: KClass<*>, private val decoder: ((values: List<String>) -> Any?)?, private val encoder: ((value: Any?) -> List<String>)?) : ConversionService {

  fun fromValues(values: List<String>, type: TypeInfo): Any?
  fun toValues(value: Any?): List<String>
}

{id="DelegatingConversionService"}