or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/maven-io-ktor--ktor-client-content-negotiation-jvm

Ktor client Content Negotiation plugin that provides automatic serialization and deserialization of request and response body content using various serialization formats like JSON, XML, and ProtoBuf

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
mavenpkg:maven/io.ktor/ktor-client-content-negotiation-jvm@3.2.x

To install, run

npx @tessl/cli install tessl/maven-io-ktor--ktor-client-content-negotiation-jvm@3.2.0

index.mddocs/

Ktor Client Content Negotiation

Ktor client Content Negotiation plugin that provides automatic serialization and deserialization of request and response body content using various serialization formats like JSON, XML, and ProtoBuf. It handles content type negotiation by automatically setting Accept headers based on configured converters, processes incoming response content based on Content-Type headers, and provides extensible architecture for custom content converters.

Package Information

  • Package Name: ktor-client-content-negotiation-jvm
  • Package Type: maven
  • Language: Kotlin
  • Installation:
    implementation("io.ktor:ktor-client-content-negotiation:3.2.0")

Core Imports

import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.*
import io.ktor.http.*

Basic Usage

import io.ktor.client.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*

val client = HttpClient {
    install(ContentNegotiation) {
        json() // Install JSON converter
    }
}

// Automatic serialization on request
val response = client.post("https://api.example.com/users") {
    setBody(User(name = "Alice", email = "alice@example.com"))
}

// Automatic deserialization on response
val user: User = client.get("https://api.example.com/users/1").body()

Architecture

The ContentNegotiation plugin is built around several key components:

  • Plugin Configuration: ContentNegotiationConfig class for registering converters and configuring behavior
  • Content Converters: ContentConverter interface implementations for specific serialization formats
  • Content Type Matching: ContentTypeMatcher interface for flexible content type matching
  • Type Filtering: Configurable ignored types that bypass content negotiation
  • Request/Response Transformation: Automatic handling of Accept headers and content serialization/deserialization

Capabilities

Plugin Installation and Configuration

Install and configure the ContentNegotiation plugin with custom converters and settings.

val ContentNegotiation: ClientPlugin<ContentNegotiationConfig>

@KtorDsl
class ContentNegotiationConfig : Configuration {
    var defaultAcceptHeaderQValue: Double?
    
    override fun <T : ContentConverter> register(
        contentType: ContentType,
        converter: T,
        configuration: T.() -> Unit
    )
    
    fun <T : ContentConverter> register(
        contentTypeToSend: ContentType,
        converter: T,
        contentTypeMatcher: ContentTypeMatcher,
        configuration: T.() -> Unit
    )
    
    inline fun <reified T> ignoreType()
    fun ignoreType(type: KClass<*>)
    inline fun <reified T> removeIgnoredType()
    fun removeIgnoredType(type: KClass<*>)
    fun clearIgnoredTypes()
}

Usage Examples:

val client = HttpClient {
    install(ContentNegotiation) {
        // Register JSON converter
        json()
        
        // Register custom converter
        register(ContentType.Application.Xml, XmlConverter()) {
            // Configure converter
        }
        
        // Set Accept header quality value
        defaultAcceptHeaderQValue = 0.8
        
        // Ignore specific types
        ignoreType<String>()
        removeIgnoredType<ByteArray>()
    }
}

Content Type Matching

Flexible content type matching for JSON and custom formats.

/**
 * Matcher that accepts all extended json content types
 */
object JsonContentTypeMatcher : ContentTypeMatcher {
    override fun contains(contentType: ContentType): Boolean {
        if (contentType.match(ContentType.Application.Json)) {
            return true
        }
        val value = contentType.withoutParameters().toString()
        return value in ContentType.Application && value.endsWith("+json", ignoreCase = true)
    }
}

/**
 * Interface for any objects that can match a ContentType.
 */
interface ContentTypeMatcher {
    /**
     * Checks if this type matches a contentType type.
     */
    fun contains(contentType: ContentType): Boolean
}

Usage Examples:

// Use built-in JSON matcher for extended JSON types
install(ContentNegotiation) {
    register(
        contentTypeToSend = ContentType.Application.Json,
        converter = JsonConverter(),
        contentTypeMatcher = JsonContentTypeMatcher
    )
}

// Create custom matcher
val customMatcher = object : ContentTypeMatcher {
    override fun contains(contentType: ContentType): Boolean {
        return contentType.match(ContentType.Application.Any) && 
               contentType.toString().endsWith("+custom")
    }
}

Content Conversion

Core interfaces for implementing custom content converters.

/**
 * A custom content converter that could be registered in ContentNegotiation plugin
 * for any particular content type. Could provide bi-directional conversion implementation.
 * One of the most typical examples is a JSON content converter that provides both
 * serialization and deserialization.
 */
interface ContentConverter {
    /**
     * Serializes a [value] to the specified [contentType] to a [OutgoingContent].
     * This function could ignore value if it is not suitable for conversion and return `null`
     * so in this case other registered converters could be tried.
     *
     * @param charset response charset
     * @param typeInfo response body typeInfo
     * @param contentType to which this data converter has been registered
     * @param value to be converted
     * @return a converted [OutgoingContent] value, or null if [value] isn't suitable
     */
    suspend fun serialize(
        contentType: ContentType,
        charset: Charset,
        typeInfo: TypeInfo,
        value: Any?
    ): OutgoingContent?
    
    /**
     * Deserializes [content] to the value of type [typeInfo]
     * @return a converted value (deserialized) or `null` if not suitable for this converter
     */
    suspend fun deserialize(
        charset: Charset,
        typeInfo: TypeInfo,
        content: ByteReadChannel
    ): Any?
}

/**
 * Configuration for client and server ContentNegotiation plugin
 */
interface Configuration {
    fun <T : ContentConverter> register(
        contentType: ContentType,
        converter: T,
        configuration: T.() -> Unit = {}
    )
}

Usage Examples:

class CustomConverter : ContentConverter {
    override suspend fun serialize(
        contentType: ContentType,
        charset: Charset,
        typeInfo: TypeInfo,
        value: Any?
    ): OutgoingContent? {
        return when (value) {
            is MyCustomType -> TextContent(
                value.serialize(),
                contentType.withCharset(charset)
            )
            else -> null
        }
    }
    
    override suspend fun deserialize(
        charset: Charset,
        typeInfo: TypeInfo,
        content: ByteReadChannel
    ): Any? {
        if (typeInfo.type == MyCustomType::class) {
            val text = content.readUTF8Line()
            return MyCustomType.deserialize(text)
        }
        return null
    }
}

Request-specific Content Type Exclusion

Exclude specific content types from Accept headers on a per-request basis.

fun HttpRequestBuilder.exclude(vararg contentType: ContentType)

Usage Examples:

// Exclude JSON from a specific request
val response = client.get("https://api.example.com/data") {
    exclude(ContentType.Application.Json)
}

// Exclude multiple content types
client.post("https://api.example.com/upload") {
    exclude(ContentType.Application.Json, ContentType.Application.Xml)
    setBody(rawData)
}

Error Handling

Handle content conversion errors with detailed exception information.

/**
 * Base exception for content conversion errors
 */
open class ContentConvertException(
    message: String,
    cause: Throwable? = null
) : Exception(message, cause)

/**
 * JSON-specific conversion exception
 */
class JsonConvertException(
    message: String,
    cause: Throwable? = null
) : ContentConvertException(message, cause)

/**
 * Exception for content conversion failures specific to ContentNegotiation plugin
 */
class ContentConverterException(message: String) : Exception(message)

Usage Examples:

try {
    val result: MyType = client.get("https://api.example.com/data").body()
} catch (e: JsonConvertException) {
    println("JSON conversion failed: ${e.message}")
    // Handle JSON-specific error
} catch (e: ContentConvertException) {
    println("Content conversion failed: ${e.message}")
    // Handle general content conversion error
} catch (e: ContentConverterException) {
    println("ContentNegotiation plugin error: ${e.message}")
    // Handle plugin-specific error
}

Utility Functions

Helper functions for charset detection and content processing.

/**
 * Detect suitable charset for an application call by Accept header or fallback to defaultCharset
 */
fun Headers.suitableCharset(defaultCharset: Charset = Charsets.UTF_8): Charset

/**
 * Detect suitable charset for an application call by Accept header or fallback to null
 */
fun Headers.suitableCharsetOrNull(defaultCharset: Charset = Charsets.UTF_8): Charset?

Usage Examples:

// In a custom converter
override suspend fun deserialize(
    charset: Charset,
    typeInfo: TypeInfo,
    content: ByteReadChannel
): Any? {
    val headers = /* get headers from context */
    val detectedCharset = headers.suitableCharset(Charsets.UTF_8)
    // Use detected charset for processing
}

Types

// Core plugin type
val ContentNegotiation: ClientPlugin<ContentNegotiationConfig>

// Configuration class
@KtorDsl
class ContentNegotiationConfig : Configuration {
    var defaultAcceptHeaderQValue: Double?
}

// JSON content type matcher
object JsonContentTypeMatcher : ContentTypeMatcher

// Exception classes for conversion failures
open class ContentConvertException(message: String, cause: Throwable? = null) : Exception
class JsonConvertException(message: String, cause: Throwable? = null) : ContentConvertException
class ContentConverterException(message: String) : Exception

// Extension function for request exclusion
fun HttpRequestBuilder.exclude(vararg contentType: ContentType)

// Utility functions for charset handling
fun Headers.suitableCharset(defaultCharset: Charset = Charsets.UTF_8): Charset
fun Headers.suitableCharsetOrNull(defaultCharset: Charset = Charsets.UTF_8): Charset?

Default Ignored Types

By default, the following types are ignored and bypass content negotiation:

Common (All Platforms)

  • ByteArray::class
  • String::class
  • HttpStatusCode::class
  • ByteReadChannel::class
  • OutgoingContent::class

Platform-Specific

  • JVM: InputStream::class
  • JS/WASM: No additional types
  • Native: No additional types

Integration with Serialization Libraries

The plugin integrates with various Ktor serialization libraries:

// JSON with kotlinx.serialization
install(ContentNegotiation) {
    json()
}

// JSON with custom configuration
install(ContentNegotiation) {
    json(Json {
        prettyPrint = true
        ignoreUnknownKeys = true
    })
}

// XML support
install(ContentNegotiation) {
    xml()
}

// Multiple formats
install(ContentNegotiation) {
    json()
    xml()
    cbor()
}