1. 概述
本文将深入探讨 Jackson 在 Kotlin 环境下的使用方式,重点覆盖对象与集合的序列化/反序列化实践,并结合常用注解如 @JsonProperty
和 @JsonInclude
进行配置优化。
对于使用 Kotlin 构建后端服务的开发者来说,Jackson 是最主流的 JSON 处理库之一。但 Kotlin 的语言特性(如数据类、默认参数、不可变属性等)与 Java 存在差异,直接使用原生 Jackson 可能会踩坑 ❌。因此,必须配合 jackson-module-kotlin
才能正确处理 Kotlin 类型系统。
✅ 正确集成后,你可以无缝实现:
- 数据类(data class)到 JSON 的自动映射
- 默认值字段缺失时的容错反序列化
- 使用注解自定义字段名和输出策略
下面我们一步步来看如何配置并高效使用。
2. Maven 配置
要让 Jackson 支持 Kotlin,核心是引入官方提供的模块依赖:
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
<version>2.9.8</version>
</dependency>
📌 注意:该模块底层依赖 kotlin-reflect
,请确保项目中已包含:
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>1.3.21</version>
</dependency>
最新版本可参考 Maven Central。
⚠️ 踩坑提示:如果未注册 KotlinModule
或创建方式不正确,即使加了依赖也可能出现 Cannot construct instance of ...
异常。
3. 对象序列化
我们以一个典型的 Movie
数据类为例:
data class Movie(
var name: String,
var studio: String,
var rating: Float? = 1f
)
创建支持 Kotlin 的 ObjectMapper
有两种推荐方式创建兼容 Kotlin 的 ObjectMapper
实例:
方式一:使用快捷工厂方法 ✅ 推荐新手使用
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
val mapper = jacksonObjectMapper()
方式二:手动注册 KotlinModule
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
val mapper = ObjectMapper().registerModule(KotlinModule())
两种方式效果一致,前者更简洁;后者便于后续扩展其他 Module(如 JavaTimeModule)。
序列化示例
@Test
fun whenSerializeMovie_thenSuccess() {
val movie = Movie("Endgame", "Marvel", 9.2f)
val serialized = mapper.writeValueAsString(movie)
val json = """
{
"name":"Endgame",
"studio":"Marvel",
"rating":9.2
}
""".trimIndent()
assertEquals(serialized, json.trimIndent())
}
输出结果为标准 JSON 格式,字段名与属性名一一对应。
4. 对象反序列化
反序列化是从 JSON 字符串重建 Kotlin 对象的过程。
基本用法
@Test
fun whenDeserializeMovie_thenSuccess() {
val json = """{"name":"Endgame","studio":"Marvel","rating":9.2}"""
val movie: Movie = mapper.readValue(json)
assertEquals(movie.name, "Endgame")
assertEquals(movie.studio, "Marvel")
assertEquals(movie.rating, 9.2f)
}
📌 关键点:**无需显式传入 TypeReference
**,只需声明变量类型即可,这是 jackson-module-kotlin
提供的语法糖。
你也可以写成泛型形式:
val movie = mapper.readValue<Movie>(json)
两者等价。
缺失字段处理 —— 利用默认值
当 JSON 中缺少某个字段时,Jackson 会尝试使用类中定义的默认值:
@Test
fun whenDeserializeMovieWithMissingValue_thenUseDefaultValue() {
val json = """{"name":"Endgame","studio":"Marvel"}""" // 缺少 rating
val movie: Movie = mapper.readValue(json)
assertEquals(movie.rating, 1f) // 使用了默认值
}
这在接口兼容性升级时非常有用,新增可选字段不影响旧客户端。
4.1 必填字段缺失导致反序列化失败
⚠️ 重点来了:Kotlin 的非空类型(non-null type)不允许为 null。若 JSON 中缺失这些字段,Jackson 将抛出异常。
例如 Movie
中的 name
和 studio
是非空字段:
@Test
fun whenMissingRequiredParameterOnDeserialize_thenFails() {
val json = """{"studio":"Marvel","rating":9.2}""" // name 缺失
val exception = assertThrows<MissingKotlinParameterException> {
mapper.readValue<Movie>(json)
}
assertEquals("name", exception.parameter.name)
assertEquals(String::class, exception.parameter.type.classifier)
}
抛出的是 MissingKotlinParameterException
,明确指出哪个参数缺失以及其类型信息。
✅ 解决方案:
- 若字段可为空,改为
String?
- 或提供默认值:
var name: String = "Unknown"
否则必须保证 JSON 包含所有非空构造参数。
5. Map 的序列化与反序列化
Kotlin 中的 Map
结构也能被 Jackson 正确处理。
序列化示例
@Test
fun whenSerializeMap_thenSuccess() {
val map = mapOf(1 to "one", 2 to "two")
val serialized = mapper.writeValueAsString(map)
val json = """
{
"1":"one",
"2":"two"
}
""".trimIndent()
assertEquals(serialized, json.trimIndent())
}
注意:Map 的 key 被自动转为字符串。
反序列化要点
反序列化时 必须明确指定泛型类型,否则无法正确还原:
@Test
fun whenDeserializeMap_thenSuccess() {
val json = """{"1":"one","2":"two"}"""
val aMap: Map<Int, String> = mapper.readValue(json)
assertEquals(aMap[1], "one")
assertEquals(aMap[2], "two")
}
❌ 错误示范:
// 不推荐:类型擦除导致运行时问题
val aMap = mapper.readValue(json) as Map<Int, String> // ⚠️ ClassCastException 风险
✅ 正确做法始终是通过变量声明或泛型指定完整类型。
6. 集合(Collection)操作
List、Set 等集合类型的处理逻辑类似。
序列化 List
@Test
fun whenSerializeList_thenSuccess() {
val movie1 = Movie("Endgame", "Marvel", 9.2f)
val movie2 = Movie("Shazam", "Warner Bros", 7.6f)
val movieList = listOf(movie1, movie2)
val serialized = mapper.writeValueAsString(movieList)
val json = """
[
{
"name":"Endgame",
"studio":"Marvel",
"rating":9.2
},
{
"name":"Shazam",
"studio":"Warner Bros",
"rating":7.6
}
]
""".trimIndent()
assertEquals(serialized, json.trimIndent())
}
反序列化 List
同样需要声明目标类型:
@Test
fun whenDeserializeList_thenSuccess() {
val json = """[
{"name":"Endgame","studio":"Marvel","rating":9.2},
{"name":"Shazam","studio":"Warner Bros","rating":7.6}
]"""
val movieList: List<Movie> = mapper.readValue(json)
val expected1 = Movie("Endgame", "Marvel", 9.2f)
val expected2 = Movie("Shazam", "Warner Bros", 7.6f)
assertTrue(movieList.contains(expected1))
assertTrue(movieList.contains(expected2))
}
⚠️ 踩坑提醒:不要用 ArrayList<Movie>
接收,应优先使用不可变类型 List<Movie>
,避免不必要的 mutable 副作用。
7. 修改字段名称 —— @JsonProperty
有时 Java/Kotlin 属性命名规范(驼峰)与 JSON 要求(如小写下划线)不一致,可用 @JsonProperty
映射。
示例:重命名 authorName → author
data class Book(
var title: String,
@JsonProperty("author") var authorName: String
)
序列化效果
@Test
fun whenSerializeBook_thenSuccess() {
val book = Book("Oliver Twist", "Charles Dickens")
val serialized = mapper.writeValueAsString(book)
val json = """
{
"title":"Oliver Twist",
"author":"Charles Dickens"
}
""".trimIndent()
assertEquals(serialized, json.trimIndent())
}
反序列化也生效
@Test
fun whenDeserializeBook_thenSuccess() {
val json = """{"title":"Oliver Twist", "author":"Charles Dickens"}"""
val book: Book = mapper.readValue(json)
assertEquals(book.title, "Oliver Twist")
assertEquals(book.authorName, "Charles Dickens")
}
✅ 单向映射搞定大小写/命名风格差异,非常实用。
8. 排除空字段 —— @JsonInclude
默认情况下,即使字段为空或为 null,也会出现在序列化结果中。
添加可空字段 genres
data class Book(
var title: String,
@JsonProperty("author") var authorName: String
) {
var genres: List<String>? = emptyList()
}
默认行为:包含空字段
@Test
fun whenSerializeBook_thenSuccess() {
val book = Book("Oliver Twist", "Charles Dickens")
val serialized = mapper.writeValueAsString(book)
val json = """
{
"title":"Oliver Twist",
"author":"Charles Dickens",
"genres":[]
}
""".trimIndent()
assertEquals(serialized, json.trimIndent())
}
这对前端可能造成困扰,比如误判 genres
有内容。
使用 @JsonInclude 控制输出
@JsonInclude(JsonInclude.Include.NON_EMPTY)
data class Book(
var title: String,
@JsonProperty("author") var authorName: String
) {
var genres: List<String>? = emptyList()
}
此时再序列化:
@Test
fun givenJsonInclude_whenSerializeBook_thenEmptyFieldExcluded() {
val book = Book("Oliver Twist", "Charles Dickens")
val serialized = mapper.writeValueAsString(book)
val json = """
{
"title":"Oliver Twist",
"author":"Charles Dickens"
}
""".trimIndent()
assertEquals(serialized, json.trimIndent())
}
✅ 效果:genres
因为空列表被排除。
📌 支持的策略还包括:
NON_NULL
:排除 null 值NON_DEFAULT
:排除默认值(如 0、false)ALWAYS
:始终包含(默认)
根据业务需求选择合适策略,提升 API 清洁度。
9. 总结
本文系统讲解了 Jackson 在 Kotlin 项目中的正确打开方式:
- ✅ 必须引入
jackson-module-kotlin
并注册KotlinModule
- ✅ 使用
jacksonObjectMapper()
快速构建兼容实例 - ✅ 数据类支持默认值、非空校验、字段重命名
- ✅ 集合与 Map 反序列化需显式声明泛型类型
- ✅ 利用
@JsonProperty
解决命名冲突 - ✅ 使用
@JsonInclude(NON_EMPTY)
减少冗余输出
📌 最佳实践建议:
- 所有 DTO 使用 data class + 默认值增强健壮性
- 统一配置全局
ObjectMapper
bean,避免重复创建 - 生产环境开启
FAIL_ON_UNKNOWN_PROPERTIES = false
防止因字段新增导致解析失败
掌握这些技巧后,你在 Spring Boot 或 Ktor 项目中处理 JSON 将更加得心应手 💪。