or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.md

index.mddocs/

0

# 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

```