Content negotiation and serialization
The ContentNegotiation plugin serves two primary purposes:
Negotiating media types between the client and server. For this, it uses the Accept
and Content-Type
headers.
Serializing/deserializing the content in a specific format. Ktor supports the following formats out-of-the-box: JSON, XML, CBOR, and ProtoBuf.
Add dependencies
ContentNegotiation
To use ContentNegotiation
, you need to include the ktor-server-content-negotiation
artifact in the build script:
implementation("io.ktor:ktor-server-content-negotiation:$ktor_version")
implementation "io.ktor:ktor-server-content-negotiation:$ktor_version"
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-content-negotiation-jvm</artifactId>
<version>${ktor_version}</version>
</dependency>
Note that serializers for specific formats require additional artifacts. For example, kotlinx.serialization requires the ktor-serialization-kotlinx-json
dependency for JSON.
Serialization
Before using kotlinx.serialization converters, you need to add the Kotlin serialization plugin as described in the Setup section.
JSON
To serialize/deserialize JSON data, you can choose one of the following libraries: kotlinx.serialization, Gson, or Jackson.
Add the ktor-serialization-kotlinx-json
artifact in the build script:
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")
implementation "io.ktor:ktor-serialization-kotlinx-json:$ktor_version"
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-serialization-kotlinx-json-jvm</artifactId>
<version>${ktor_version}</version>
</dependency>
Add the ktor-serialization-gson
artifact in the build script:
implementation("io.ktor:ktor-serialization-gson:$ktor_version")
implementation "io.ktor:ktor-serialization-gson:$ktor_version"
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-serialization-gson-jvm</artifactId>
<version>${ktor_version}</version>
</dependency>
Add the ktor-serialization-jackson
artifact in the build script:
implementation("io.ktor:ktor-serialization-jackson:$ktor_version")
implementation "io.ktor:ktor-serialization-jackson:$ktor_version"
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-serialization-jackson-jvm</artifactId>
<version>${ktor_version}</version>
</dependency>
XML
To serialize/deserialize XML, add the ktor-serialization-kotlinx-xml
in the build script:
implementation("io.ktor:ktor-serialization-kotlinx-xml:$ktor_version")
implementation "io.ktor:ktor-serialization-kotlinx-xml:$ktor_version"
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-serialization-kotlinx-xml-jvm</artifactId>
<version>${ktor_version}</version>
</dependency>
Note that XML serialization is supported on JVM only.
CBOR
To serialize/deserialize CBOR, add the ktor-serialization-kotlinx-cbor
in the build script:
implementation("io.ktor:ktor-serialization-kotlinx-cbor:$ktor_version")
implementation "io.ktor:ktor-serialization-kotlinx-cbor:$ktor_version"
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-serialization-kotlinx-cbor-jvm</artifactId>
<version>${ktor_version}</version>
</dependency>
ProtoBuf
To serialize/deserialize ProtoBuf, add the ktor-serialization-kotlinx-protobuf
in the build script:
implementation("io.ktor:ktor-serialization-kotlinx-protobuf:$ktor_version")
implementation "io.ktor:ktor-serialization-kotlinx-protobuf:$ktor_version"
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-serialization-kotlinx-protobuf-jvm</artifactId>
<version>${ktor_version}</version>
</dependency>
Install ContentNegotiation
To install the ContentNegotiation
plugin to the application, pass it to the install
function in the specified module. The code snippets below show how to install ContentNegotiation
...
... 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.contentnegotiation.*
fun main() {
embeddedServer(Netty, port = 8080) {
install(ContentNegotiation)
// ...
}.start(wait = true)
}
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
// ...
fun Application.module() {
install(ContentNegotiation)
// ...
}
Ktor supports the following formats out-of-the-box: JSON, XML, CBOR. You can also implement your own custom serializer.
JSON serializer
To register the JSON serializer in your application, call the json
method:
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
install(ContentNegotiation) {
json()
}
The json
method also allows you to adjust serialization settings provided by JsonBuilder, for example:
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
})
}
To register the Gson serializer in your application, call the gson
method:
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.serialization.gson.*
install(ContentNegotiation) {
gson()
}
The gson
method also allows you to adjust serialization settings provided by GsonBuilder, for example:
install(ContentNegotiation) {
gson {
setDateFormat(DateFormat.LONG)
setPrettyPrinting()
}
}
To register the Jackson serializer in your application, call the jackson
method:
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.serialization.jackson.*
install(ContentNegotiation) {
jackson()
}
The jackson
method also allows you to adjust serialization settings provided by ObjectMapper, for example:
install(ContentNegotiation) {
jackson {
configure(SerializationFeature.INDENT_OUTPUT, true)
setDefaultPrettyPrinter(DefaultPrettyPrinter().apply {
indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance)
indentObjectsWith(DefaultIndenter(" ", "\n"))
})
registerModule(JavaTimeModule()) // support java.time.* types
}
}
XML serializer
To register the XML serializer in your application, call the xml
method:
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.xml.*
install(ContentNegotiation) {
xml()
}
The xml
method also allows you to access XML serialization settings, for example:
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.xml.*
import nl.adaptivity.xmlutil.*
import nl.adaptivity.xmlutil.serialization.*
install(ContentNegotiation) {
xml(format = XML {
xmlDeclMode = XmlDeclMode.Charset
})
}
CBOR serializer
To register the CBOR serializer in your application, call the cbor
method:
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.cbor.*
install(ContentNegotiation) {
cbor()
}
The cbor
method also allows you to access CBOR serialization settings provided by CborBuilder, for example:
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.cbor.*
import kotlinx.serialization.cbor.*
install(ContentNegotiation) {
cbor(Cbor {
ignoreUnknownKeys = true
})
}
ProtoBuf serializer
To register the ProtoBuf serializer in your application, call the protobuf
method:
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.protobuf.*
install(ContentNegotiation) {
protobuf()
}
The protobuf
method also allows you to access ProtoBuf serialization settings provided by ProtoBufBuilder, for example:
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.protobuf.*
import kotlinx.serialization.protobuf.*
install(ContentNegotiation) {
protobuf(ProtoBuf {
encodeDefaults = true
})
}
Custom serializer
To register a custom serializer for a specified Content-Type
, you need to call the register
method. In the example below, two custom serializer are registered to deserialize application/json
and application/xml
data:
install(ContentNegotiation) {
register(ContentType.Application.Json, CustomJsonConverter())
register(ContentType.Application.Xml, CustomXmlConverter())
}
Receive and send data
Create a data class
To deserialize received data into an object, you need to create a data class, for example:
data class Customer(val id: Int, val firstName: String, val lastName: String)
If you use kotlinx.serialization, make sure that this class has the @Serializable
annotation:
import kotlinx.serialization.*
@Serializable
data class Customer(val id: Int, val firstName: String, val lastName: String)
Serializing/deserializing of the following types is supported by the kotlinx.serialization library:
Receive data
To receive and convert a content for a request, call the receive
method that accepts a data class as a parameter:
post("/customer") {
val customer = call.receive<Customer>()
customerStorage.add(customer)
call.respondText("Customer stored correctly", status = HttpStatusCode.Created)
}
The Content-Type
of the request will be used to choose a serializer for processing the request. The example below shows a sample HTTP client request containing JSON or XML data that is converted to a Customer
object on the server side:
POST http://0.0.0.0:8080/customer
Content-Type: application/json
{
"id": 3,
"firstName": "Jet",
"lastName": "Brains"
}
POST http://0.0.0.0:8080/customer
Content-Type: application/xml
<Customer id="3" firstName="Jet" lastName="Brains"/>
You can find the full example here: json-kotlinx.
Send data
To pass a data object in a response, you can use the respond
method:
get("/customer/{id}") {
val id = call.parameters["id"]
val customer: Customer = customerStorage.find { it.id == id!!.toInt() }!!
call.respond(customer)
}
In this case, Ktor uses the Accept
header to choose the required serializer. You can find the full example here: json-kotlinx.
Implement a custom serializer
In Ktor, you can write your own serializer for serializing/deserializing data. To do this, you need to implement the ContentConverter interface:
interface ContentConverter {
suspend fun serialize(contentType: ContentType, charset: Charset, typeInfo: TypeInfo, value: Any): OutgoingContent?
suspend fun deserialize(charset: Charset, typeInfo: TypeInfo, content: ByteReadChannel): Any?
}
Take a look at the GsonConverter class as an implementation example.
Last modified: 02 April 2024