Rob Elliot
04/14/2023, 10:51 AMMap<String, Any>
. I want to use a delegated property to extract a key and assign it to a val
without repetition:
val aMap: Map<String, Any> = mapOf(
"foo" to "bar"
)
val foo: String by aMap // much nicer than `val foo: String = aMap["foo"] as String`
BUT, I actually want to wrap the value of foo
into a class, while still using the property delegate to extract the value from the map... something like this:
val aMap: Map<String, Any> = mapOf(
"foo" to "bar"
)
data class Wrapper(val wrapped: String)
val foo: Wrapper by aMap // obviously can't compile, the value is a String not a Wrapper...
val foo = Wrapper(by aMap) // also can't compile...
val foo = (by aMap).let { Wrapper(it) } // you're just being silly now
Is this possible?Riccardo Lippolis
04/14/2023, 10:57 AMval aMap: Map<String, Any> = mapOf(
"foo" to "bar"
)
class Wrapper {
val foo by aMap
}
val fooWrapper = Wrapper()
println(fooWrapper.foo) // prints "bar"
Rob Elliot
04/14/2023, 10:57 AMWrapper
can't know about aMap
directlyRiccardo Lippolis
04/14/2023, 10:58 AMval aMap: Map<String, Any> = mapOf(
"foo" to "bar"
)
class Wrapper(someMap: Map<String, Any>) {
val foo by someMap
}
val fooWrapper = Wrapper(aMap)
println(fooWrapper.foo)
Rob Elliot
04/14/2023, 11:00 AMWrapper
needs to just take a single String or I'll have to make hundreds of different Wrapper
classesWout Werkman
04/14/2023, 11:25 AMval foo: Wrapper by aMap
valid syntax..Rob Elliot
04/14/2023, 11:29 AMRob Elliot
04/14/2023, 12:29 PMimport kotlin.properties.ReadOnlyProperty
fun <V1, V2> Map<String, Any?>.transform(
mapper: (V1) -> V2
): ReadOnlyProperty<Any, V2?> = ReadOnlyProperty { _, prop ->
val rawValue = this[prop.name]
if (rawValue == null) rawValue
else mapper(rawValue as V1)
}
data class Wrapper(val wrapped: String)
data class IntWrapper(val wrapped: Int)
val aMap: Map<String, Any?> = mapOf(
"foo" to "bar",
"num" to 5,
"nullable" to null,
)
val foo by aMap.transform(::Wrapper)
val num by aMap.transform(::IntWrapper)
val nullable by aMap.transform(::IntWrapper)
David Kubecka
04/14/2023, 12:45 PMMap<String, V1?>
? That way you could avoid the cast.David Kubecka
04/14/2023, 12:47 PMRob Elliot
04/14/2023, 12:47 PMDavid Kubecka
04/14/2023, 12:48 PMRob Elliot
04/14/2023, 12:49 PMDavid Kubecka
04/14/2023, 12:51 PMobjectMapper.convertValue
.
Your imperative of avoiding reflection is due to performance?Rob Elliot
04/14/2023, 12:52 PMDavid Kubecka
04/14/2023, 12:54 PMRob Elliot
04/14/2023, 1:33 PMDavid Kubecka
04/14/2023, 1:39 PMDavid Kubecka
04/14/2023, 1:40 PMMapWrapper
and then repeat the same process on it.Wout Werkman
04/14/2023, 2:16 PMkotlinx.serialization
's JsonSerialization are a very powerful tool and have very little tradeoff when it comes to performance as well as clarity of exceptions and documentation. And my favourite feature is that they are simple data classes that can easily be used for tests.
I'm curious to hear the reason to do this manually 🙂Rob Elliot
04/17/2023, 8:21 AMRob Elliot
04/17/2023, 8:39 AMfun JsonObject.uuid() = ReadOnlyProperty<Any, UUID> { _, prop ->
UUID.fromString(string(prop.name))
}
I've got a String, I need a UUID, so I call UUID.fromString
.
I don't need to know special annotations or how to register converters. I don't need compiler plugins. If something I didn't expect happens it's absolutely trivial to drop in a breakpoint and see what's going on.
2. It's non-destructive - I don't throw away the JSON keys I didn't know about, I just expose the ones I did
Negatives:
1. I haven't worked out nice error handling. Jackson at least is pretty good at telling you where in the JSON structure it is.
2. It would need work to make it less memory inefficient - pretty sure most JSON parsers convert to objects on the fly, rather than reading the whole JSON document into memory as untyped objects and then converting it into domain relevant objects. I suspect something could be done here.
3. The resulting objects are (at the moment) hard to construct new instances from - much harder than a data class instance with its copy method. Again I have a vague notion that there are ways to improve that, but I haven't worked them out...
4. Needs some thought around null handling / missing keys - apart from anything else it's always bothered me the way that json parsers tend to elide the absence of a key in an object and its presence but with value null, they aren't the same thing... one of the few cases where I quite like `Option`/`Optional` because it's nestable, a Scala val with type Option[Option[String]]
can have values Some(Some(value))
, Some(None)
and None
which is quite a nice way to capture this. I guess in Kotlin you could make it Optional<String?>?
...Wout Werkman
04/17/2023, 9:03 AM?
syntax for Optional
and also allow nested chaining. In Swift Int?? != Int?