我有一个带有可为空属性的类
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
属性的不可为空性。
一种解决方案是创建一个具有非null属性data class RequestModel(val description: String)
的新数据类。
但我正在寻找一种通用方法,以避免在每个用例中创建新类。
理想的通用解决方案:
fun validate(model: RequestModel): NoNullableField<RequestModel>
如何以通用方式从具有可空属性的类的属性中删除可空性?使用某种Kotlin编译器合约有用吗?
答案 0 :(得分:1)
您可以使用Kotlin reflection获取所有属性并检查它们是否不为空:
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
答案 1 :(得分:1)
首先,如果要使用抽象的可验证对象,则需要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"
}
}