Skip to content

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-webspring-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 还提供了 JsonObjectSerializerJsonObjectDeserializer 基类,可以简化对象的序列化处理:

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,为用户提供优秀的数据交换体验。 🎉