Appearance
Spring Boot JSON 处理详解 ☕
概述
在现代 Web 开发中,JSON 已经成为数据交换的标准格式。Spring Boot 为了让开发者能够轻松处理 JSON 数据,提供了对三大主流 JSON 库的集成支持:
- Jackson(默认首选)
- Gson
- JSON-B
NOTE
Jackson 是 Spring Boot 的默认 JSON 处理库,也是官方推荐的选择。它不仅功能强大,而且与 Spring 生态系统集成度最高。
为什么需要 JSON 处理? 🤔
在没有统一的 JSON 处理方案之前,开发者面临以下痛点:
- 手动转换:需要手写大量的对象与 JSON 字符串互转代码
- 格式不统一:不同开发者可能使用不同的序列化规则
- 性能问题:缺乏优化的序列化/反序列化机制
- 维护困难:当对象结构变化时,需要同步修改多处转换逻辑
Spring Boot 的 JSON 集成正是为了解决这些问题,提供开箱即用的、高性能的、可配置的 JSON 处理能力。
Jackson:默认的 JSON 处理引擎
自动配置机制
当你在项目中引入 spring-boot-starter-web
或 spring-boot-starter-json
时,Jackson 会被自动配置:
kotlin
// 无需手动配置,Spring Boot 自动创建 ObjectMapper Bean
@RestController
class UserController {
@GetMapping("/users/{id}")
fun getUser(@PathVariable id: Long): User {
// 返回的 User 对象会自动序列化为 JSON
return User(id = id, name = "张三", age = 25)
}
@PostMapping("/users")
fun createUser(@RequestBody user: User): User {
// 请求体中的 JSON 会自动反序列化为 User 对象
println("接收到用户: ${user.name}")
return user
}
}
data class User(
val id: Long,
val name: String,
val age: Int
)
自定义序列化器和反序列化器
有时候,我们需要对特定类型的数据进行特殊的 JSON 处理。传统的做法是注册 Jackson 模块,但 Spring Boot 提供了更简单的 @JsonComponent
注解。
解决的问题场景
假设我们有一个日期时间字段,需要以特定格式进行序列化:
kotlin
// 传统方式:需要手动注册模块
@Configuration
class JacksonConfig {
@Bean
fun objectMapper(): ObjectMapper {
val mapper = ObjectMapper()
val module = SimpleModule()
module.addSerializer(LocalDateTime::class.java, CustomDateTimeSerializer())
module.addDeserializer(LocalDateTime::class.java, CustomDateTimeDeserializer())
mapper.registerModule(module)
return mapper
}
}
kotlin
// Spring Boot 方式:使用 @JsonComponent 注解
@JsonComponent
class DateTimeJsonComponent {
class Serializer : JsonSerializer<LocalDateTime>() {
override fun serialize(value: LocalDateTime, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(value.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
}
}
class Deserializer : JsonDeserializer<LocalDateTime>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): LocalDateTime {
return LocalDateTime.parse(parser.text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
}
}
}
完整的业务示例
让我们看一个更实际的例子,处理金额字段的序列化:
点击查看完整的金额处理示例
kotlin
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import org.springframework.boot.jackson.JsonComponent
import java.math.BigDecimal
import java.math.RoundingMode
/**
* 金额处理的 JSON 组件
* 解决问题:
* 1. 金额序列化时保留两位小数
* 2. 金额反序列化时进行精度控制
* 3. 避免浮点数精度问题
*/
@JsonComponent
class MoneyJsonComponent {
/**
* 金额序列化器
* 将 BigDecimal 格式化为保留两位小数的字符串
*/
class Serializer : JsonSerializer<BigDecimal>() {
override fun serialize(value: BigDecimal, gen: JsonGenerator, serializers: SerializerProvider) {
// 保留两位小数,四舍五入
val formattedValue = value.setScale(2, RoundingMode.HALF_UP)
gen.writeString(formattedValue.toString())
}
}
/**
* 金额反序列化器
* 将字符串转换为 BigDecimal,并进行精度控制
*/
class Deserializer : JsonDeserializer<BigDecimal>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): BigDecimal {
val text = parser.text
return try {
BigDecimal(text).setScale(2, RoundingMode.HALF_UP)
} catch (e: NumberFormatException) {
throw IllegalArgumentException("无效的金额格式: $text")
}
}
}
}
// 使用示例
data class Product(
val id: Long,
val name: String,
val price: BigDecimal // 会使用自定义的序列化器处理
)
@RestController
class ProductController {
@GetMapping("/products/{id}")
fun getProduct(@PathVariable id: Long): Product {
return Product(
id = id,
name = "iPhone 15",
price = BigDecimal("8999.999") // 序列化时会变成 "8999.00"
)
}
}
使用 JsonObjectSerializer 简化代码
Spring Boot 还提供了 JsonObjectSerializer
和 JsonObjectDeserializer
基类,可以简化对象的序列化处理:
kotlin
@JsonComponent
class UserJsonComponent {
/**
* 使用 JsonObjectSerializer 简化用户对象序列化
* 优势:自动处理 JSON 对象的开始和结束标记
*/
class Serializer : JsonObjectSerializer<User>() {
override fun serializeObject(value: User, gen: JsonGenerator, provider: SerializerProvider) {
gen.writeStringField("name", value.name)
gen.writeNumberField("age", value.age)
// 添加计算字段
gen.writeStringField("ageGroup", getAgeGroup(value.age))
}
private fun getAgeGroup(age: Int): String {
return when {
age < 18 -> "未成年"
age < 60 -> "成年人"
else -> "老年人"
}
}
}
/**
* 使用 JsonObjectDeserializer 简化用户对象反序列化
* 优势:提供空值安全的字段提取方法
*/
class Deserializer : JsonObjectDeserializer<User>() {
override fun deserializeObject(
parser: JsonParser,
context: DeserializationContext,
codec: ObjectCodec,
tree: JsonNode
): User {
val name = nullSafeValue(tree["name"], String::class.java)
val age = nullSafeValue(tree["age"], Int::class.java)
// 数据验证
if (name.isNullOrBlank()) {
throw IllegalArgumentException("用户名不能为空")
}
if (age < 0 || age > 150) {
throw IllegalArgumentException("年龄必须在 0-150 之间")
}
return User(name = name, age = age)
}
}
}
Jackson Mixins:优雅的注解混入
Mixins 是 Jackson 的一个强大特性,允许我们为第三方类添加 JSON 注解,而无需修改原始类。
解决的问题
假设我们使用了一个第三方库的类,但需要自定义其 JSON 序列化行为:
kotlin
// 第三方库的类,我们无法修改
class ThirdPartyUser {
var username: String = ""
var password: String = "" // 敏感信息,不应该序列化
var email: String = ""
var internalId: String = "" // 内部字段,不应该暴露
}
// 使用 Mixin 为第三方类添加 JSON 注解
@JsonMixin(ThirdPartyUser::class)
abstract class ThirdPartyUserMixin {
@JsonIgnore // 忽略密码字段
abstract var password: String
@JsonIgnore // 忽略内部 ID
abstract var internalId: String
@JsonProperty("user_name") // 重命名字段
abstract var username: String
}
TIP
Spring Boot 会自动扫描应用包中标注了 @JsonMixin
的类,并将它们注册到 ObjectMapper
中。这样就能在不修改第三方类的情况下,控制其 JSON 序列化行为。
请求响应处理流程
让我们通过时序图来理解 Spring Boot 中 JSON 处理的完整流程:
其他 JSON 库支持
Gson
如果你更喜欢使用 Google 的 Gson 库:
kotlin
// 添加依赖后,Spring Boot 会自动配置 Gson
dependencies {
implementation("com.google.code.gson:gson")
// 移除 Jackson 依赖(如果不需要的话)
}
// 自定义 Gson 配置
@Configuration
class GsonConfig {
@Bean
fun gsonBuilderCustomizer(): GsonBuilderCustomizer {
return GsonBuilderCustomizer { builder ->
builder.setDateFormat("yyyy-MM-dd HH:mm:ss")
.setPrettyPrinting()
}
}
}
JSON-B
Jakarta EE 标准的 JSON-B 也得到了支持:
kotlin
// 添加 JSON-B 依赖
dependencies {
implementation("jakarta.json.bind:jakarta.json.bind-api")
implementation("org.eclipse:yasson") // 推荐的实现
}
IMPORTANT
虽然 Spring Boot 支持多种 JSON 库,但建议在一个项目中只使用一种,以避免潜在的冲突和混淆。
最佳实践与注意事项
1. 性能优化
性能建议
- 对于高频序列化的对象,考虑使用
@JsonComponent
进行优化 - 避免在序列化过程中进行复杂的计算或 I/O 操作
- 合理使用
@JsonIgnore
减少不必要的字段序列化
2. 安全考虑
安全提醒
- 始终使用
@JsonIgnore
隐藏敏感字段(如密码、内部 ID 等) - 对反序列化的数据进行验证,防止恶意输入
- 考虑使用 DTO 模式,而不是直接序列化实体类
3. 错误处理
kotlin
@JsonComponent
class SafeUserJsonComponent {
class Deserializer : JsonObjectDeserializer<User>() {
override fun deserializeObject(
parser: JsonParser,
context: DeserializationContext,
codec: ObjectCodec,
tree: JsonNode
): User {
return try {
val name = nullSafeValue(tree["name"], String::class.java)
val age = nullSafeValue(tree["age"], Int::class.java)
// 数据验证
validateUser(name, age)
User(name = name, age = age)
} catch (e: Exception) {
throw JsonProcessingException("用户数据格式错误: ${e.message}")
}
}
private fun validateUser(name: String?, age: Int?) {
if (name.isNullOrBlank()) {
throw IllegalArgumentException("用户名不能为空")
}
if (age == null || age < 0 || age > 150) {
throw IllegalArgumentException("年龄必须在 0-150 之间")
}
}
}
}
总结
Spring Boot 的 JSON 处理能力为现代 Web 开发提供了强大而灵活的支持:
✅ 开箱即用:无需复杂配置,自动集成主流 JSON 库
✅ 高度可定制:通过 @JsonComponent
和 Mixins 实现精细控制
✅ 性能优化:内置优化的序列化/反序列化机制
✅ 安全可靠:提供多种安全特性和错误处理机制
通过合理使用这些特性,我们可以构建出既高效又安全的 JSON API,为用户提供优秀的数据交换体验。 🎉