Kotlin通过地图委托属性如果地图

时间:2018-04-22 19:39:11

标签: kotlin

我有一个kotlin对象定义如下:

data class UserUpdateRequest(val map: Map<String, Any?>) {
    @get:Email
    val email: String? by map
    val firstName: String? by map
    val lastName: String? by map
}

这样工作得很好,所以我遇到的问题是属性可以为空,当我访问一个时,比如说instance.email它会抛出一个NoSuchElementException如果所说的属性不是在地图中设置。

相反,它返回null会更方便,因为它是可选的/可空的。有没有办法在不编写我自己的代表的情况下实现这一目标?

2 个答案:

答案 0 :(得分:10)

您基本上可以使用.withDefault { ... }扩展包装Map进行委派,以便它执行lambda来计算缺席密钥的值:

data class UserUpdateRequest(val map: Map<String, Any?>) {
    private val defaultMap = map.withDefault { null }

    @get:Email
    val email: String? by defaultMap
    val firstName: String? by defaultMap
    val lastName: String? by defaultMap
}

请注意,此包装器不处理简单的defaultMap.get(key)defaultMap[key]查询,它只影响defaulMap.getValue(key)调用(委托实现也恰好使用它)。

答案 1 :(得分:1)

如果您想更好地控制委托的工作并了解其工作原理,那么这里是委托的实现。

首先演示如何在您的方案中使用它:

data class UserUpdateRequest(val map: Map<String, Any?>) {

        var email: String? by valueFromMap(map = map)
        var firstName: String? by valueFromMap(map = map)
        var lastName: String? by valueFromMap(map = map)
        var age: Int? by valueFromMap(map = map)
    }

    val myInitialMap = mutableMapOf<String,Any?>()
    myInitialMap["email"] = "1234@something.com"
    myInitialMap["age"] = 34

    val request = UserUpdateRequest(myInitialMap)
    request.lastName = "Genericname"

    println("email: ${request.email}, firstNme: ${request.firstName}, lastName: ${request.lastName}, age: ${request.age}")

    request.lastName = null

    println("lastName after setting to null: ${request.lastName}")

这将打印:
电子邮件:1234@something.com,名字:null,姓氏:通用名称,年龄:34
设置为null后的lastName:null

因此,如果变量存在且其类型与变量类型匹配,则将其设置为initialMap的值。否则将变量设置为null。

委托实现

fun <R>valueFromMap(key: (KProperty<*>) -> String = KProperty<*>::name,
                map: Map<String, Any?>): ReadWriteProperty<Any, R?>{

    val myMap = map.toMutableMap()

    return object : ReadWriteProperty<Any, R?> {

    override fun getValue(thisRef: Any, property: KProperty<*>): R? {
        return (myMap.getOrDefault(key(property), null) as? R)?: return null
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: R?) {
        if(value == null){
            myMap.remove(key(property))
        } else {
            myMap[key(property)] = value
        }
    }
}

}

因此valueFromMap(map = map)是一个为val / var(KProperty)返回ReadWriteProperty的函数,该函数使用KPproperty的名称作为键在地图中查找作为参数传递的值。
因此,当我们致电:

request.lastName = "Genericname"

在上面的示例中,实际上我们调用。

initialMap["lastName"] = "Genericname"

如果变量名称和相应的键不同,则您甚至可以将键作为参数传递。
使用这种方法,您可以通过getValue和setValue函数自行处理值访问。

希望这对任何人都有帮助。