Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin data class and Bean validation: @NotNull on Long fields does not work

Here is data class:

data class CreateCommentRequest(
        @field:NotNull
        @field:NotEmpty
        val comment: String,

        @field:NotNull
        val postId: Long
)

If I omit comment field, validation fails properly.

But if I do not pass postId, like this:

{
    "comment": "hello"
}

validation is passed.

How to make validation fail if postId is not passed?

P.S. I use spring boot 2.0.1 with com.fasterxml.jackson.module:jackson-module-kotlin module, kotlin version: 1.2.20

like image 745
Teimuraz Avatar asked Apr 18 '18 10:04

Teimuraz


1 Answers

I think this is related to https://github.com/FasterXML/jackson-module-kotlin/issues/130.

It looks like you can turn on the DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, but that will not cause a validation error, but it will fail to construct the object. You'll need to handle that exception appropriately to generate a 400 error.

If you really want validation, you need to allow the object to be constructed, so that the validator can run, which means making the primitive fields nullable:

data class CreateCommentRequest(
    @field:NotNull
    @field:NotEmpty
    val comment: String,

    @field:NotNull
    val postId: Long?
)

This probably seems counter-intuitive, but the validator doesn't run until the object is constructed. It's not part of jackson (which doesn't care about the validation annotations) or kotlin. Unless you've provided an alternative implementation, it's the hibernate validator that is doing the null checking based on the annotations, and it needs a complete object to be able to perform this checking.

It looks like the jackson kotlin module is creating an instance of the object, but it has to provide a value for the postId, so it is providing a default value of 0 (unless you instruct it to fail on null primitives).

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule

data class Test(val x : Long)

fun main(args : Array<String>) {
    val m = ObjectMapper().registerModule(KotlinModule())
    print(m.readValue("{}", Test::class.java)) // prints Test(x=0) 
}

By making the field nullable, you're allowing it to be constructed, so that the JSR 303 validation can run on the object.

like image 59
Peter Hart Avatar answered Nov 19 '22 13:11

Peter Hart