Search Icon

Ryan Harrison My blog, portfolio and technology related ramblings

Using Ktor with Jackson Serialization

Although the preferred JSON serialization library used in a lot of the Ktor examples is GSON, which makes sense due to it’s simplicity and ease of use, in real-world use Jackson is probably the preferred option. It’s faster (especially when combined with the AfterBurner module) and generally more flexible. Ktor comes with a built-in feature that makes use Jackson for JSON conversion very simple.

Example real-world project: https://github.com/raharrison/lynks-server

Add Jackson dependency

In your build.gradle file add a dependency to the Ktor Jackson artifact:

dependencies {
    implementation "io.ktor:ktor-server-content-negotiation:$ktor_version"
    implementation "io.ktor:ktor-serialization-jackson:$ktor_version"
}

This will add the Ktor JacksonConverter class which can then be used within the standard ContentNegotiation feature.

It also includes an implicit dependency on the Jackson Kotlin Module which must be installed in order for Jackson to handle data classes (which do not have an empty default constructor as Jackson expects).

Install as a Converter

Then tell Ktor to use Jackson for serialization/deserialization for JSON content:

install(ContentNegotiation) {
    jackson {
        // customize the Jackson serializer as usual
        configure(SerializationFeature.INDENT_OUTPUT, true)
        setDefaultPrettyPrinter(DefaultPrettyPrinter().apply {
            indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance)
        })
    }
}

which is the same as doing:

install(ContentNegotiation) {
    register(ContentType.Application.Json, JacksonConverter())
}

With the converter installed, any request to your Ktor server will be served with a JSON response as long as the Content-Type header accepts it.

Reuse an Existing Mapper

The above configuration is quick and easy, however ObjectMapper instances are heavy objects and their configuration is generally shared across various areas of your app. Therefore, instead of creating a new ObjectMapper within the Ktor feature itself, initialise one for your application and point Ktor to it. You can then reuse the same mapper when needed without re-initialising it every time:

object JsonMapper {
    // automatically installs the Kotlin module
    val defaultMapper: ObjectMapper = jacksonObjectMapper()

    init {
        defaultMapper.configure(SerializationFeature.INDENT_OUTPUT, true)
        defaultMapper.registerModule(JavaTimeModule())
    }
}

then use the alternate syntax to install the converter, passing in our pre-made ObjectMapper instance:

install(ContentNegotiation) {
    register(ContentType.Application.Json, JacksonConverter(defaultMapper))
}

You are then free to reuse the same JsonMapper.defaultMapper object across the rest of your app.