Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to validate json in kotlin

There is a kotlin class with the following structure.

data class Person(
    @field:Length(max = 5)
    val name: String,
    val phones: List<Phone>
)

data class Phone(
    @field:Length(max = 10)
    val number: String
)

When converting the json string through objectMapper, I want to receive all the violation values.

ex) JSON object is not valid. Reasons (3) name length must be 5, number length must be 10, ...

@Test
fun test() {
    val json = """
        {
            "name": "name",
            "phones": [
                { "number": "1234567890123456" },
                { "number": "1234567890123456" }
            ]
        }
    """.trimIndent()
    try {
        objectMapper.readValue(json, Person::class.java)
    } catch (ex: ConstraintViolationException) {
        val violations = ex.constraintViolations
        println(violations.size) // expected size = 3
    }
}

However, the above code fails to catch the exception and causes the exception below.

com.fasterxml.jackson.databind.JsonMappingException: JSON object is not valid. Reasons (1): {"bean":"Phone","property":"number","value":"1234567890123456","message": "..."},  (through reference chain: Person["phones"]->java.util.ArrayList[0])

Looking at the reason, those wrapped in a list do not throw ConstructionViolationException, but throw JsonMappingException.

below dependencies

plugins {
    id("org.springframework.boot") version "2.4.3"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    kotlin("jvm") version "1.4.30"
    kotlin("plugin.spring") version "1.4.30"
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-validation")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}
class BeanValidationDeserializer(base: BeanDeserializerBase?) : BeanDeserializer(base) {

    private val logger = LoggerFactory.getLogger(this::class.java)
    private val validator = Validation.buildDefaultValidatorFactory().validator

    override fun deserialize(parser: JsonParser?, ctxt: DeserializationContext?): Any {
        val instance = super.deserialize(parser, ctxt)
        validate(instance)
        return instance
    }

    private fun validate(instance: Any) {
        val violations = validator.validate(instance)
        if (violations.isNotEmpty()) {
            val message = StringBuilder()
            message.append("JSON object is not valid. Reasons (").append(violations.size).append("): ")
            for (violation in violations) {
                message.append("{\"bean\":\"${violation.rootBeanClass.name}\",")
                    .append("\"property\":\"${violation.propertyPath}\",")
                    .append("\"value\":\"${violation.invalidValue}\",")
                    .append("\"message\": \"${violation.message}\"}")
                    .append(", ")
            }
            logger.warn(message.toString())
            throw ConstraintViolationException(message.toString(), violations)
        }
    }
}

@Bean
fun objectMapper(): ObjectMapper = Jackson2ObjectMapperBuilder.json()
    .featuresToDisable(MapperFeature.DEFAULT_VIEW_INCLUSION)
    .featuresToDisable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
    .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
    .modules(ParameterNamesModule(), JavaTimeModule(), Jdk8Module(), KotlinModule(), customValidationModule())
    .build()

@Bean
fun customValidationModule(): SimpleModule {
    val validationModule = SimpleModule()
    validationModule.setDeserializerModifier(object : BeanDeserializerModifier() {
        override fun modifyDeserializer(
            config: DeserializationConfig?,
            beanDesc: BeanDescription?,
            deserializer: JsonDeserializer<*>?
        ): JsonDeserializer<*>? {
            return if (deserializer is BeanDeserializer) {
                BeanValidationDeserializer(deserializer as BeanDeserializer?)
            } else deserializer
        }
    })
    return validationModule
}

I'm not sure how to do it. I ask for your help.

like image 628
counter_punch_ Avatar asked May 18 '26 18:05

counter_punch_


1 Answers

I would say an easier and more maintainable way would be to define a JSON Schema.

After that is in place, you can use one of the two json validation libraries mentioned here (https://json-schema.org/implementations.html#validator-kotlin) to validate your json.

like image 194
rbs Avatar answered May 23 '26 07:05

rbs



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!