如何在Kotlin中实现JSON全量校验并获取所有违规结果
解决方案
最简适配方案
无需修改Jackson反序列化逻辑,先完成反序列化再统一执行校验,天然支持获取全量嵌套校验违规,代码如下:
import javax.validation.Validator import com.fasterxml.jackson.databind.ObjectMapper import javax.validation.ConstraintViolation // 注入Spring容器管理的Validator和ObjectMapper @Service class JsonValidationService( private val validator: Validator, private val objectMapper: ObjectMapper ) { fun validatePersonJson(json: String): Set<ConstraintViolation<Person>> { // 先完整反序列化出对象,Jackson默认不会触发字段校验 val person = objectMapper.readValue(json, Person::class.java) // 执行全量校验,会自动递归校验所有嵌套字段、集合内的Phone对象 return validator.validate(person) } }
该方案默认就能返回你预期的3个校验违规项,不需要处理异常包装逻辑,适配Spring Boot 2.4.3版本的所有默认依赖。
若必须在反序列化过程中执行校验的改造方案
如果你有强需求需要把校验逻辑耦合到Jackson反序列化流程中,可按以下步骤调整:
1. 定义校验违规收集上下文
用ThreadLocal存储反序列化全链路的校验违规,避免线程安全问题:
import javax.validation.ConstraintViolation object ValidationContext { private val violations = ThreadLocal<MutableSet<ConstraintViolation<*>>>() fun init() = violations.set(mutableSetOf()) fun add(violation: ConstraintViolation<*>) = violations.get()?.add(violation) fun getAll(): Set<ConstraintViolation<*>> = violations.get() ?: emptySet() fun clear() = violations.remove() }
2. 改造自定义校验反序列化器
校验后仅收集违规不抛出异常,确保反序列化流程可以遍历所有嵌套节点:
import com.fasterxml.jackson.databind.deser.std.StdDeserializer import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.databind.DeserializationContext import com.fasterxml.jackson.databind.JsonDeserializer import javax.validation.Validator class BeanValidationDeserializer( private val defaultDeserializer: JsonDeserializer<Any>, private val validator: Validator ) : StdDeserializer<Any>(defaultDeserializer.handledType()) { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Any { val targetObj = defaultDeserializer.deserialize(p, ctxt) // 校验当前对象,把违规加入上下文 validator.validate(targetObj).forEach { ValidationContext.add(it) } return targetObj } }
3. 注册反序列化器到ObjectMapper
关闭Jackson的异常包装配置,避免校验异常被包裹:
import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier import com.fasterxml.jackson.databind.BeanDescription import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import javax.validation.Validator @Configuration class JacksonConfig { @Bean fun customObjectMapper(validator: Validator): ObjectMapper { val module = SimpleModule().apply { setDeserializerModifier(object : BeanDeserializerModifier() { override fun modifyDeserializer( config: DeserializationConfig, beanDesc: BeanDescription, deserializer: JsonDeserializer<*> ): JsonDeserializer<*> { // 仅给带校验注解的类包装校验逻辑 val hasValidationAnnotation = beanDesc.beanClass.annotations.any { it.annotationClass.java.packageName.startsWith("javax.validation.constraints") } return if (hasValidationAnnotation) { BeanValidationDeserializer(deserializer as JsonDeserializer<Any>, validator) } else deserializer } }) } return ObjectMapper() .registerModule(module) .disable(DeserializationFeature.WRAP_EXCEPTIONS) } }
4. 封装反序列化校验入口
统一处理上下文的初始化和资源释放:
import com.fasterxml.jackson.databind.ObjectMapper import javax.validation.ConstraintViolation fun <T> deserializeWithValidation(json: String, clazz: Class<T>, objectMapper: ObjectMapper): Result<T> { ValidationContext.init() return try { val result = objectMapper.readValue(json, clazz) val violations = ValidationContext.getAll() if (violations.isEmpty()) Result.success(result) else Result.failure(RuntimeException(violations.joinToString())) } finally { ValidationContext.clear() } }
注意事项
- Spring Boot 2.4.3使用的是
javax.validation包,不要误导入jakarta.validation相关依赖 - 若要避免ThreadLocal使用,可通过
DeserializationContext.setAttribute()传递违规集合,在校验反序列化器中获取存入即可
内容的提问来源于stack exchange,提问作者counter_punch_
相关产品推荐
相关产品推荐

