Валидация данных в Kotlin: библиотеки и подходы

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Валидация данных в Kotlin: обзор библиотек и практические примеры

Валидация данных — один из ключевых аспектов разработки надёжных приложений. В экосистеме Kotlin существует несколько зрелых библиотек, которые позволяют декларативно описывать правила проверки, избавляя разработчика от шаблонного кода. В этой статье мы рассмотрим три популярных инструмента: Konform, Valiktor и Kotlinx Validation, а также обсудим, когда и какой инструмент выбрать.

1. Konform — функциональная валидация на основе Arrow

Библиотека Konform построена на базе функционального фреймворка Arrow. Она использует концепцию Validated для накопления ошибок и поддерживает как простые, так и вложенные структуры данных.

Установка

// build.gradle.kts
implementation("io.konform:konform:0.4.0")

Пример использования

import io.konform.validation.*

data class User(
    val name: String,
    val email: String,
    val age: Int
)

val userValidation = Validation<User> {
    User::name {
        minLength(2) hint "Имя должно содержать минимум 2 символа"
        maxLength(50) hint "Имя не должно превышать 50 символов"
    }
    User::email {
        pattern(Regex("^[\\w.-]+@[\\w.-]+\\.\\w{2,}$")) hint "Некорректный email"
    }
    User::age {
        minimum(18) hint "Возраст должен быть не менее 18 лет"
        maximum(120) hint "Укажите реальный возраст"
    }
}

fun main() {
    val user = User("A", "invalid-email", 15)
    val result = userValidation(user)
    
    if (result.errors.isNotEmpty()) {
        result.errors.forEach { error ->
            println("Поле: ${error.path} — ${error.message}")
        }
    } else {
        println("Валидация пройдена успешно")
    }
}

Особенности

  • Полная интеграция с Arrow для композиции валидаций
  • Поддержка вложенных объектов и списков
  • Типобезопасные DSL-конструкции
  • Накопление всех ошибок (не останавливается на первой)

2. Valiktor — императивная валидация с fluent API

Valiktor предлагает более традиционный подход с использованием fluent-интерфейса. Библиотека не требует функциональных зависимостей и проста в освоении для разработчиков, знакомых с Java.

Установка

implementation("org.valiktor:valiktor-core:0.12.0")

Пример использования

import org.valiktor.functions.*
import org.valiktor.validate

data class Order(
    val id: String,
    val amount: Double,
    val items: List<String>
)

fun validateOrder(order: Order) {
    validate(order) {
        validate(Order::id).isNotBlank().hasSize(10..36)
        validate(Order::amount).isPositive().isLessThanOrEqualTo(10000.0)
        validate(Order::items).isNotEmpty().hasSize(1..50)
    }
}

fun main() {
    val order = Order("", -100.0, emptyList())
    try {
        validateOrder(order)
        println("Заказ корректен")
    } catch (e: Exception) {
        println("Ошибка валидации: ${e.message}")
    }
}

Особенности

  • Простой и понятный API без функциональных абстракций
  • Выбрасывает исключение при первой ошибке (режим fail-fast)
  • Поддержка кастомных валидаторов через расширения
  • Лёгкая интеграция с Java-проектами

3. Kotlinx Validation — официальная библиотека от JetBrains

Хотя проект Kotlinx Validation пока находится в экспериментальной стадии, он уже предлагает интересный подход с аннотациями и рефлексией, напоминающий Javax Validation (Bean Validation).

Установка

implementation("org.jetbrains.kotlinx:kotlinx-validation:0.1.0")

Пример использования

import kotlinx.validation.*
import kotlinx.validation.constraints.*

data class Product(
    @get:NotBlank
    val title: String,
    
    @get:Positive
    @get:Max(1000.0)
    val price: Double,
    
    @get:Size(min = 1, max = 10)
    val categories: List<String>
)

fun main() {
    val product = Product("", -5.0, emptyList())
    val violations = validate(product)
    
    if (violations.isNotEmpty()) {
        violations.forEach { violation ->
            println("${violation.propertyPath}: ${violation.message}")
        }
    } else {
        println("Продукт валиден")
    }
}

Рекомендаций