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
npx @tessl/cli install tessl/maven-io-ktor--ktor-client-content-negotiation-jvm@3.2.00
# Ktor Client Content Negotiation
1
2
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.
3
4
## Package Information
5
6
- **Package Name**: ktor-client-content-negotiation-jvm
7
- **Package Type**: maven
8
- **Language**: Kotlin
9
- **Installation**:
10
```kotlin
11
implementation("io.ktor:ktor-client-content-negotiation:3.2.0")
12
```
13
14
## Core Imports
15
16
```kotlin
17
import io.ktor.client.plugins.contentnegotiation.*
18
import io.ktor.serialization.*
19
import io.ktor.http.*
20
```
21
22
## Basic Usage
23
24
```kotlin
25
import io.ktor.client.*
26
import io.ktor.client.plugins.contentnegotiation.*
27
import io.ktor.serialization.kotlinx.json.*
28
29
val client = HttpClient {
30
install(ContentNegotiation) {
31
json() // Install JSON converter
32
}
33
}
34
35
// Automatic serialization on request
36
val response = client.post("https://api.example.com/users") {
37
setBody(User(name = "Alice", email = "alice@example.com"))
38
}
39
40
// Automatic deserialization on response
41
val user: User = client.get("https://api.example.com/users/1").body()
42
```
43
44
## Architecture
45
46
The ContentNegotiation plugin is built around several key components:
47
48
- **Plugin Configuration**: `ContentNegotiationConfig` class for registering converters and configuring behavior
49
- **Content Converters**: `ContentConverter` interface implementations for specific serialization formats
50
- **Content Type Matching**: `ContentTypeMatcher` interface for flexible content type matching
51
- **Type Filtering**: Configurable ignored types that bypass content negotiation
52
- **Request/Response Transformation**: Automatic handling of Accept headers and content serialization/deserialization
53
54
## Capabilities
55
56
### Plugin Installation and Configuration
57
58
Install and configure the ContentNegotiation plugin with custom converters and settings.
59
60
```kotlin { .api }
61
val ContentNegotiation: ClientPlugin<ContentNegotiationConfig>
62
63
@KtorDsl
64
class ContentNegotiationConfig : Configuration {
65
var defaultAcceptHeaderQValue: Double?
66
67
override fun <T : ContentConverter> register(
68
contentType: ContentType,
69
converter: T,
70
configuration: T.() -> Unit
71
)
72
73
fun <T : ContentConverter> register(
74
contentTypeToSend: ContentType,
75
converter: T,
76
contentTypeMatcher: ContentTypeMatcher,
77
configuration: T.() -> Unit
78
)
79
80
inline fun <reified T> ignoreType()
81
fun ignoreType(type: KClass<*>)
82
inline fun <reified T> removeIgnoredType()
83
fun removeIgnoredType(type: KClass<*>)
84
fun clearIgnoredTypes()
85
}
86
```
87
88
**Usage Examples:**
89
90
```kotlin
91
val client = HttpClient {
92
install(ContentNegotiation) {
93
// Register JSON converter
94
json()
95
96
// Register custom converter
97
register(ContentType.Application.Xml, XmlConverter()) {
98
// Configure converter
99
}
100
101
// Set Accept header quality value
102
defaultAcceptHeaderQValue = 0.8
103
104
// Ignore specific types
105
ignoreType<String>()
106
removeIgnoredType<ByteArray>()
107
}
108
}
109
```
110
111
### Content Type Matching
112
113
Flexible content type matching for JSON and custom formats.
114
115
```kotlin { .api }
116
/**
117
* Matcher that accepts all extended json content types
118
*/
119
object JsonContentTypeMatcher : ContentTypeMatcher {
120
override fun contains(contentType: ContentType): Boolean {
121
if (contentType.match(ContentType.Application.Json)) {
122
return true
123
}
124
val value = contentType.withoutParameters().toString()
125
return value in ContentType.Application && value.endsWith("+json", ignoreCase = true)
126
}
127
}
128
129
/**
130
* Interface for any objects that can match a ContentType.
131
*/
132
interface ContentTypeMatcher {
133
/**
134
* Checks if this type matches a contentType type.
135
*/
136
fun contains(contentType: ContentType): Boolean
137
}
138
```
139
140
**Usage Examples:**
141
142
```kotlin
143
// Use built-in JSON matcher for extended JSON types
144
install(ContentNegotiation) {
145
register(
146
contentTypeToSend = ContentType.Application.Json,
147
converter = JsonConverter(),
148
contentTypeMatcher = JsonContentTypeMatcher
149
)
150
}
151
152
// Create custom matcher
153
val customMatcher = object : ContentTypeMatcher {
154
override fun contains(contentType: ContentType): Boolean {
155
return contentType.match(ContentType.Application.Any) &&
156
contentType.toString().endsWith("+custom")
157
}
158
}
159
```
160
161
### Content Conversion
162
163
Core interfaces for implementing custom content converters.
164
165
```kotlin { .api }
166
/**
167
* A custom content converter that could be registered in ContentNegotiation plugin
168
* for any particular content type. Could provide bi-directional conversion implementation.
169
* One of the most typical examples is a JSON content converter that provides both
170
* serialization and deserialization.
171
*/
172
interface ContentConverter {
173
/**
174
* Serializes a [value] to the specified [contentType] to a [OutgoingContent].
175
* This function could ignore value if it is not suitable for conversion and return `null`
176
* so in this case other registered converters could be tried.
177
*
178
* @param charset response charset
179
* @param typeInfo response body typeInfo
180
* @param contentType to which this data converter has been registered
181
* @param value to be converted
182
* @return a converted [OutgoingContent] value, or null if [value] isn't suitable
183
*/
184
suspend fun serialize(
185
contentType: ContentType,
186
charset: Charset,
187
typeInfo: TypeInfo,
188
value: Any?
189
): OutgoingContent?
190
191
/**
192
* Deserializes [content] to the value of type [typeInfo]
193
* @return a converted value (deserialized) or `null` if not suitable for this converter
194
*/
195
suspend fun deserialize(
196
charset: Charset,
197
typeInfo: TypeInfo,
198
content: ByteReadChannel
199
): Any?
200
}
201
202
/**
203
* Configuration for client and server ContentNegotiation plugin
204
*/
205
interface Configuration {
206
fun <T : ContentConverter> register(
207
contentType: ContentType,
208
converter: T,
209
configuration: T.() -> Unit = {}
210
)
211
}
212
```
213
214
**Usage Examples:**
215
216
```kotlin
217
class CustomConverter : ContentConverter {
218
override suspend fun serialize(
219
contentType: ContentType,
220
charset: Charset,
221
typeInfo: TypeInfo,
222
value: Any?
223
): OutgoingContent? {
224
return when (value) {
225
is MyCustomType -> TextContent(
226
value.serialize(),
227
contentType.withCharset(charset)
228
)
229
else -> null
230
}
231
}
232
233
override suspend fun deserialize(
234
charset: Charset,
235
typeInfo: TypeInfo,
236
content: ByteReadChannel
237
): Any? {
238
if (typeInfo.type == MyCustomType::class) {
239
val text = content.readUTF8Line()
240
return MyCustomType.deserialize(text)
241
}
242
return null
243
}
244
}
245
```
246
247
### Request-specific Content Type Exclusion
248
249
Exclude specific content types from Accept headers on a per-request basis.
250
251
```kotlin { .api }
252
fun HttpRequestBuilder.exclude(vararg contentType: ContentType)
253
```
254
255
**Usage Examples:**
256
257
```kotlin
258
// Exclude JSON from a specific request
259
val response = client.get("https://api.example.com/data") {
260
exclude(ContentType.Application.Json)
261
}
262
263
// Exclude multiple content types
264
client.post("https://api.example.com/upload") {
265
exclude(ContentType.Application.Json, ContentType.Application.Xml)
266
setBody(rawData)
267
}
268
```
269
270
### Error Handling
271
272
Handle content conversion errors with detailed exception information.
273
274
```kotlin { .api }
275
/**
276
* Base exception for content conversion errors
277
*/
278
open class ContentConvertException(
279
message: String,
280
cause: Throwable? = null
281
) : Exception(message, cause)
282
283
/**
284
* JSON-specific conversion exception
285
*/
286
class JsonConvertException(
287
message: String,
288
cause: Throwable? = null
289
) : ContentConvertException(message, cause)
290
291
/**
292
* Exception for content conversion failures specific to ContentNegotiation plugin
293
*/
294
class ContentConverterException(message: String) : Exception(message)
295
```
296
297
**Usage Examples:**
298
299
```kotlin
300
try {
301
val result: MyType = client.get("https://api.example.com/data").body()
302
} catch (e: JsonConvertException) {
303
println("JSON conversion failed: ${e.message}")
304
// Handle JSON-specific error
305
} catch (e: ContentConvertException) {
306
println("Content conversion failed: ${e.message}")
307
// Handle general content conversion error
308
} catch (e: ContentConverterException) {
309
println("ContentNegotiation plugin error: ${e.message}")
310
// Handle plugin-specific error
311
}
312
```
313
314
### Utility Functions
315
316
Helper functions for charset detection and content processing.
317
318
```kotlin { .api }
319
/**
320
* Detect suitable charset for an application call by Accept header or fallback to defaultCharset
321
*/
322
fun Headers.suitableCharset(defaultCharset: Charset = Charsets.UTF_8): Charset
323
324
/**
325
* Detect suitable charset for an application call by Accept header or fallback to null
326
*/
327
fun Headers.suitableCharsetOrNull(defaultCharset: Charset = Charsets.UTF_8): Charset?
328
```
329
330
**Usage Examples:**
331
332
```kotlin
333
// In a custom converter
334
override suspend fun deserialize(
335
charset: Charset,
336
typeInfo: TypeInfo,
337
content: ByteReadChannel
338
): Any? {
339
val headers = /* get headers from context */
340
val detectedCharset = headers.suitableCharset(Charsets.UTF_8)
341
// Use detected charset for processing
342
}
343
```
344
345
## Types
346
347
```kotlin { .api }
348
// Core plugin type
349
val ContentNegotiation: ClientPlugin<ContentNegotiationConfig>
350
351
// Configuration class
352
@KtorDsl
353
class ContentNegotiationConfig : Configuration {
354
var defaultAcceptHeaderQValue: Double?
355
}
356
357
// JSON content type matcher
358
object JsonContentTypeMatcher : ContentTypeMatcher
359
360
// Exception classes for conversion failures
361
open class ContentConvertException(message: String, cause: Throwable? = null) : Exception
362
class JsonConvertException(message: String, cause: Throwable? = null) : ContentConvertException
363
class ContentConverterException(message: String) : Exception
364
365
// Extension function for request exclusion
366
fun HttpRequestBuilder.exclude(vararg contentType: ContentType)
367
368
// Utility functions for charset handling
369
fun Headers.suitableCharset(defaultCharset: Charset = Charsets.UTF_8): Charset
370
fun Headers.suitableCharsetOrNull(defaultCharset: Charset = Charsets.UTF_8): Charset?
371
```
372
373
## Default Ignored Types
374
375
By default, the following types are ignored and bypass content negotiation:
376
377
### Common (All Platforms)
378
- `ByteArray::class`
379
- `String::class`
380
- `HttpStatusCode::class`
381
- `ByteReadChannel::class`
382
- `OutgoingContent::class`
383
384
### Platform-Specific
385
- **JVM**: `InputStream::class`
386
- **JS/WASM**: No additional types
387
- **Native**: No additional types
388
389
## Integration with Serialization Libraries
390
391
The plugin integrates with various Ktor serialization libraries:
392
393
```kotlin
394
// JSON with kotlinx.serialization
395
install(ContentNegotiation) {
396
json()
397
}
398
399
// JSON with custom configuration
400
install(ContentNegotiation) {
401
json(Json {
402
prettyPrint = true
403
ignoreUnknownKeys = true
404
})
405
}
406
407
// XML support
408
install(ContentNegotiation) {
409
xml()
410
}
411
412
// Multiple formats
413
install(ContentNegotiation) {
414
json()
415
xml()
416
cbor()
417
}
418
```