0
# Jackson Serialization
1
2
Jackson-based JSON serialization implementation for Ktor HTTP clients. Provides high-performance JSON processing with extensive configuration options, custom serializers, and advanced data binding features through the Jackson ObjectMapper.
3
4
## Capabilities
5
6
### JacksonSerializer Class
7
8
JsonSerializer implementation using Jackson's ObjectMapper for JSON processing.
9
10
```kotlin { .api }
11
/**
12
* JsonSerializer using Jackson as backend.
13
*/
14
@Deprecated("Please use ContentNegotiation plugin and its converters")
15
class JacksonSerializer(
16
jackson: ObjectMapper = jacksonObjectMapper(),
17
block: ObjectMapper.() -> Unit = {}
18
) : JsonSerializer {
19
/**
20
* Convert data object to OutgoingContent using Jackson serialization.
21
*/
22
override fun write(data: Any, contentType: ContentType): OutgoingContent
23
24
/**
25
* Read content from response using Jackson deserialization.
26
*/
27
override fun read(type: TypeInfo, body: Input): Any
28
}
29
```
30
31
## Service Registration
32
33
The JacksonSerializer is automatically discoverable on JVM through the ServiceLoader mechanism:
34
35
**META-INF/services/io.ktor.client.plugins.json.JsonSerializer**
36
```
37
io.ktor.client.plugins.jackson.JacksonSerializer
38
```
39
40
## Dependencies
41
42
To use JacksonSerializer, add the Jackson dependency to your project:
43
44
**Gradle**
45
```kotlin
46
implementation("io.ktor:ktor-client-jackson:2.3.13")
47
```
48
49
**Maven**
50
```xml
51
<dependency>
52
<groupId>io.ktor</groupId>
53
<artifactId>ktor-client-jackson-jvm</artifactId>
54
<version>2.3.13</version>
55
</dependency>
56
```
57
58
## Usage Examples
59
60
### Basic Usage
61
62
```kotlin
63
import io.ktor.client.*
64
import io.ktor.client.plugins.json.*
65
import io.ktor.client.plugins.jackson.*
66
67
val client = HttpClient {
68
install(JsonPlugin) {
69
serializer = JacksonSerializer()
70
}
71
}
72
```
73
74
### Custom ObjectMapper Configuration
75
76
```kotlin
77
val client = HttpClient {
78
install(JsonPlugin) {
79
serializer = JacksonSerializer {
80
// Configure ObjectMapper
81
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
82
configure(SerializationFeature.INDENT_OUTPUT, true)
83
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
84
85
// Property naming strategy
86
propertyNamingStrategy = PropertyNamingStrategies.SNAKE_CASE
87
88
// Date format
89
setDateFormat(SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
90
91
// Null value handling
92
setSerializationInclusion(JsonInclude.Include.NON_NULL)
93
94
// Custom modules
95
registerModule(JavaTimeModule())
96
registerModule(KotlinModule.Builder().build())
97
}
98
}
99
}
100
```
101
102
### Data Classes with Jackson
103
104
```kotlin
105
import com.fasterxml.jackson.annotation.*
106
import java.time.LocalDateTime
107
108
data class User(
109
val id: Long,
110
val name: String,
111
val email: String,
112
@JsonProperty("created_at")
113
val createdAt: LocalDateTime,
114
@JsonIgnore
115
val password: String? = null
116
)
117
118
// POST request - automatic serialization
119
val response = client.post("https://api.example.com/users") {
120
contentType(ContentType.Application.Json)
121
setBody(User(0, "Alice", "alice@example.com", LocalDateTime.now()))
122
}
123
124
// GET request - automatic deserialization
125
val user: User = client.get("https://api.example.com/users/1").body()
126
```
127
128
### Custom Serializers and Deserializers
129
130
```kotlin
131
import com.fasterxml.jackson.core.*
132
import com.fasterxml.jackson.databind.*
133
import java.time.LocalDateTime
134
import java.time.format.DateTimeFormatter
135
136
class LocalDateTimeSerializer : JsonSerializer<LocalDateTime>() {
137
private val formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME
138
139
override fun serialize(
140
value: LocalDateTime,
141
gen: JsonGenerator,
142
serializers: SerializerProvider
143
) {
144
gen.writeString(value.format(formatter))
145
}
146
}
147
148
class LocalDateTimeDeserializer : JsonDeserializer<LocalDateTime>() {
149
private val formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME
150
151
override fun deserialize(
152
parser: JsonParser,
153
context: DeserializationContext
154
): LocalDateTime {
155
return LocalDateTime.parse(parser.valueAsString, formatter)
156
}
157
}
158
159
// Usage with custom serializers
160
val client = HttpClient {
161
install(JsonPlugin) {
162
serializer = JacksonSerializer {
163
val module = SimpleModule()
164
module.addSerializer(LocalDateTime::class.java, LocalDateTimeSerializer())
165
module.addDeserializer(LocalDateTime::class.java, LocalDateTimeDeserializer())
166
registerModule(module)
167
}
168
}
169
}
170
```
171
172
### Polymorphic Serialization
173
174
```kotlin
175
import com.fasterxml.jackson.annotation.*
176
177
@JsonTypeInfo(
178
use = JsonTypeInfo.Id.NAME,
179
include = JsonTypeInfo.As.PROPERTY,
180
property = "type"
181
)
182
@JsonSubTypes(
183
JsonSubTypes.Type(value = Dog::class, name = "dog"),
184
JsonSubTypes.Type(value = Cat::class, name = "cat")
185
)
186
abstract class Animal(
187
open val name: String
188
)
189
190
@JsonTypeName("dog")
191
data class Dog(
192
override val name: String,
193
val breed: String
194
) : Animal(name)
195
196
@JsonTypeName("cat")
197
data class Cat(
198
override val name: String,
199
val color: String
200
) : Animal(name)
201
202
// Usage
203
val animals = listOf(
204
Dog("Buddy", "Golden Retriever"),
205
Cat("Whiskers", "Orange")
206
)
207
208
val response = client.post("https://api.example.com/animals") {
209
contentType(ContentType.Application.Json)
210
setBody(animals)
211
}
212
```
213
214
### Collection and Map Handling
215
216
```kotlin
217
// Generic type handling with TypeReference
218
import com.fasterxml.jackson.core.type.TypeReference
219
220
// List deserialization
221
val userListType = object : TypeReference<List<User>>() {}
222
val users: List<User> = client.get("https://api.example.com/users").body()
223
224
// Map deserialization
225
val userMapType = object : TypeReference<Map<String, User>>() {}
226
val userMap: Map<String, User> = client.get("https://api.example.com/users/map").body()
227
228
// Complex nested structures
229
data class ApiResponse<T>(
230
val data: T,
231
val status: String,
232
val message: String?
233
)
234
235
val responseType = object : TypeReference<ApiResponse<List<User>>>() {}
236
val response: ApiResponse<List<User>> = client.get("https://api.example.com/users").body()
237
```
238
239
### Error Handling
240
241
```kotlin
242
import com.fasterxml.jackson.core.*
243
import com.fasterxml.jackson.databind.*
244
245
try {
246
val user: User = client.get("https://api.example.com/users/1").body()
247
} catch (e: JsonParseException) {
248
// Malformed JSON
249
logger.error("Invalid JSON response", e)
250
} catch (e: JsonMappingException) {
251
// Mapping error (type mismatch, missing properties)
252
logger.error("JSON mapping error", e)
253
} catch (e: JsonProcessingException) {
254
// General processing error
255
logger.error("JSON processing error", e)
256
}
257
```
258
259
### Advanced ObjectMapper Configuration
260
261
```kotlin
262
val client = HttpClient {
263
install(JsonPlugin) {
264
serializer = JacksonSerializer {
265
// Deserialization features
266
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
267
configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false)
268
configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true)
269
configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true)
270
271
// Serialization features
272
configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
273
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
274
configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false)
275
configure(SerializationFeature.INDENT_OUTPUT, true)
276
277
// Property inclusion
278
setSerializationInclusion(JsonInclude.Include.NON_NULL)
279
setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
280
281
// Visibility settings
282
setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
283
setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
284
285
// Property naming strategy
286
propertyNamingStrategy = PropertyNamingStrategies.SNAKE_CASE
287
288
// Date and time handling
289
registerModule(JavaTimeModule())
290
setDateFormat(SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"))
291
292
// Kotlin support
293
registerModule(KotlinModule.Builder()
294
.withReflectionCacheSize(512)
295
.configure(KotlinFeature.NullToEmptyCollection, false)
296
.configure(KotlinFeature.NullToEmptyMap, false)
297
.configure(KotlinFeature.SingletonSupport, true)
298
.build())
299
}
300
}
301
}
302
```
303
304
### MixIn Annotations
305
306
```kotlin
307
// Using MixIn to add annotations to third-party classes
308
interface UserMixIn {
309
@JsonProperty("user_id")
310
fun getId(): Long
311
312
@JsonIgnore
313
fun getPassword(): String?
314
}
315
316
val client = HttpClient {
317
install(JsonPlugin) {
318
serializer = JacksonSerializer {
319
addMixIn(User::class.java, UserMixIn::class.java)
320
}
321
}
322
}
323
```
324
325
## Jackson Configuration Options
326
327
The JacksonSerializer supports extensive ObjectMapper configuration:
328
329
### Deserialization Features
330
- `FAIL_ON_UNKNOWN_PROPERTIES`: Control unknown property handling
331
- `FAIL_ON_NULL_FOR_PRIMITIVES`: Handle null values for primitives
332
- `ACCEPT_EMPTY_STRING_AS_NULL_OBJECT`: Empty string handling
333
- `READ_UNKNOWN_ENUM_VALUES_AS_NULL`: Enum value handling
334
335
### Serialization Features
336
- `FAIL_ON_EMPTY_BEANS`: Empty object handling
337
- `WRITE_DATES_AS_TIMESTAMPS`: Date format control
338
- `INDENT_OUTPUT`: Pretty printing
339
- `WRITE_NULL_MAP_VALUES`: Null value inclusion
340
341
### Property Inclusion
342
- `JsonInclude.Include.NON_NULL`: Exclude null values
343
- `JsonInclude.Include.NON_EMPTY`: Exclude empty collections
344
- `JsonInclude.Include.NON_DEFAULT`: Exclude default values
345
346
### Advanced Features
347
- **Custom Modules**: JavaTimeModule, KotlinModule, etc.
348
- **Property Naming**: Snake case, camel case strategies
349
- **Type Information**: Polymorphic type handling
350
- **MixIn Annotations**: Add annotations to third-party classes
351
- **Custom Serializers**: Fine-grained serialization control
352
353
## Performance Considerations
354
355
- ObjectMapper instances are thread-safe and should be reused
356
- Module registration is expensive - do it during configuration, not per request
357
- Consider using `@JsonIgnoreProperties(ignoreUnknown = true)` for resilient deserialization
358
- Custom serializers should be stateless and lightweight
359
- Use TypeReference for complex generic types to avoid type erasure issues