mscharhag, Programming and Stuff;

A blog about programming and software development topics, mostly focused on Java technologies including Java EE, Spring and Grails.

Thursday, 19 November, 2020

Validation in Kotlin: Valiktor

Bean Validation is the Java standard for validation and can be used in Kotlin as well. However, there are also two popular alternative libraries for validation available in Kotlin: Konform and Valiktor. Both implement validation in a more kotlin-like way without annotations. In this post we will look at Valiktor.

Getting started with Valiktor

First we need to add the Valiktor dependency to our project.

For Maven:

<dependency>
  <groupId>org.valiktor</groupId>
  <artifactId>valiktor-core</artifactId>
  <version>0.12.0</version>
</dependency>

For Gradle:

implementation 'org.valiktor:valiktor-core:0.12.0'

Now let's look at a simple example:

class Article(val title: String, val text: String) {
    init {
        validate(this) {
            validate(Article::text).hasSize(min = 10, max = 10000)
            validate(Article::title).isNotBlank()
        }
    }
}

Within the init block we call the validate(..) function to validate the Article object. validate(..) accepts two parameters: The object that should be validated and a validation function. In the validation function we define validation constraints for the Article class.

Now we try to create an invalid Article object with:

Article(title = "", text = "some article text")

This causes a ConstraintViolationException to be thrown because the title field is not allowed to be empty.

More validation constraints

Let's look at a few more example validation rules:

validate(this) {
    
    // Multiple constraints can be chained
    validate(Article::authorEmail)
            .isNotBlank()
            .isEmail()
            .endsWith("@cool-blog.com")

    // Nested validation
    // Checks that Article.category.name is not blank
    validate(Article::category).validate {
        validate(Category::name).isNotBlank()
    }

    // Collection validation
    // Checks that no Keyword in the keywords collection has a blank name
    validate(Article::keywords).validateForEach {
        validate(Keyword::name).isNotBlank()
    }

    // Conditional validation
    // if the article is published the permalink field cannot be blank
    if (isPublished) {
        validate(Article::permalink).isNotBlank()
    }
}

Validating objects from outside

In the previous examples the validation constraints are implemented within the objects init block. However, it is also possible to perform the validation outside the class.

For example:

val person = Person(name = "")

validate(person) {
    validate(Person::name).isNotBlank()
}

This validates the previously created Person object and causes a ConstraintViolationException to be thrown (because name is empty)

Creating a custom validation constraint

To define our own validation methods we need two things: An implementation of the Constraint interface and an extension method. The following snippet shows an example validation method to make sure an Interable<T> does not contain duplicate elements:

object NoDuplicates : Constraint

fun <E, T> Validator<E>.Property<Iterable<T>?>.hasNoDuplicates()
        = this.validate(NoDuplicates) { iterable: Iterable<T>? ->

    if (iterable == null) {
        return@validate true
    }

    val list = iterable.toList()
    val set = list.toSet()
    set.size == list.size
}

This adds a method named hasNoDuplicates() to Validator<E>.Property<Iterable<T>?>. So this method can be called for fields of type Iterable<T>. The extension method is implemented by calling validate(..) with our Constraint and passing a validation function.

In the validation function we implement the actual validation. In this example we simply convert the Iterable to a List and then the List to a Set. If duplicate elements are present both collections have a different size (a Set does not contain duplicate elements).

We can now use our hasNoDuplicates() validation method like this:

class Article(val keywords: List<Keyword>) {
    init {
        validate(this) {
            validate(Article::keywords).hasNoDuplicates()
        }
    }
}

Conclusion

Valiktor is an interesting alternative for validation in Kotlin. It provides a fluent DSL to define validation rules. Thoes rules are defined in standard Kotlin code (and not via annotations) which makes it easy to add conditional logic. Valiktor comes with many predefined validation constraints. Custom constraints easily be implemented using extension functions.

 

Leave a reply