You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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_

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 05:06:02