kotlin - убрать допустимость значения NULL из свойств класса
У меня есть класс с некоторыми свойствами, допускающими значение NULL
data class RequestModel(
val description: String?
)
и функция проверки
fun validate(model: RequestModel): RequestModel{
if(model.description == null) throw IllegalArgumentException("description must be non null")
return model
}
После этого шага проверки мне нужен способ указать недопустимость обнуления description
свойство.
Одно из решений - создать новый класс данных с ненулевым свойством. data class RequestModel(val description: String)
.
Но я ищу общий способ избежать создания новых классов для каждого варианта использования.
Идеальное универсальное решение:
fun validate(model: RequestModel): NoNullableField<RequestModel>
Как я могу удалить значение NULL из свойств класса со свойствами, допускающими значение NULL, обычным способом? Полезно ли использовать какой-то контракт компилятора kotlin?
2 ответа
Прежде всего, если вы хотите работать с абстрактными проверяемыми объектами, вам необходимо Validatable
интерфейс:
interface Validatable {
fun validate()
}
Вам также понадобится класс, представляющий проверенный объект:
data class Valid<out T : Validatable>(val obj: T) {
init {
obj.validate()
}
fun <U : Any> U?.mustBeValidated(): U = checkNotNull(this) {
"${obj::class.jvmName}.validate() successfully validated invalid object $obj"
}
}
Теперь тебе нужно Validatable.valid()
функция, которая помогает создавать Valid
экземпляры:
fun <T : Validatable> T.valid(): Valid<T> = Valid(this)
Вот как вы можете сделать свой RequestModel
быть Validatable
:
data class RequestModel(
val description: String?
) : Validatable {
override fun validate() {
requireNotNull(description) { "description must be non null" }
}
}
val Valid<RequestModel>.description get() = obj.description.mustBeValidated()
И вот как это можно использовать:
val validModel: Valid<RequestModel> = model.valid()
val notNullDescription: String = validModel.description
Вы также можете сделать Valid
класс inline. Поскольку встроенные классы не могут иметьinit
блоки init
логика перенесена в фабричный метод. И поскольку основной конструктор встроенного класса должен бытьpublic
, конструктор отмечен @Experimental private annotation class ValidInternal
что предотвращает использование незаконного конструктора:
@UseExperimental(ValidInternal::class)
fun <T : Validatable> T.valid(): Valid<T> {
validate()
return Valid(this)
}
@Experimental
private annotation class ValidInternal
inline class Valid<out T : Validatable> @ValidInternal constructor(
// Validatable is used here instead of T
// because inline class cannot have generic value parameter
private val _obj: Validatable
) {
@Suppress("UNCHECKED_CAST") // _obj is supposed to be T
val obj: T
get() = _obj as T
fun <U : Any> U?.mustBeValidated(): U = checkNotNull(this) {
"${obj::class}.validate() successfully validated invalid object $obj"
}
}
Вы можете использовать отражение Kotlin, чтобы получить все свойства и проверить, не равны ли они нулю:
inline fun <reified T : Any> T.requireNoNullableProperties() = NoNullableProperties(this, T::class)
class NoNullableProperties<out T : Any>(val obj: T, clazz: KClass<T>) {
init {
clazz.memberProperties.forEach { prop ->
if (prop.returnType.isMarkedNullable) {
prop.isAccessible = true
requireNotNull(prop.get(obj)) {
"${prop.name} must be not null, obj - [$obj]"
}
}
}
}
operator fun <R> get(property: KProperty1<in T, R?>): R = requireNotNull(property.get(obj)) {
"Extension and mutable properties can't be validated, property - [$property], obj - [$obj]"
}
}
Пример использования:
val validated = model.requireNoNullableProperties()
val description: String = validated[RequestModel::description]
Также вы можете извлечь validated[RequestModel::description]
к свойству расширения NoNullableProperties<RequestModel>
:
val ValidRequestModel.description get() = get(RequestModel::description)
где ValidRequestModel
является:
typealias ValidRequestModel = NoNullableProperties<RequestModel>
Пример использования:
val validated = model.requireNoNullableProperties()
val description: String = validated.description