How to Deserialize Jackson container with Generics Generically - kotlin

I have a signature for a method that looks like this:
inline fun <reified TData: IBulkModel?> bulkCreate(path: String) {
val type = jacksonTypeRef<RequestListWrapper<TData>>()
}
There's more to it, but this is the pertinent portion. I have a refied T here in an inline function. My expectation is that the T here would be the actual T for the function for any given call to this, but it's not, it's IBulkModel.
Is there a way to make this work with Kotlin, or am I stuck passing in the complete class?

Nested type parameters are lost, even in reified parameters. The only type preserved is the top-level one.
Jackson has a solution for this; you can use the type factory from Java:
data class Generic<T>(val t: T)
fun main(args: Array<String>)
{
val mapper = ObjectMapper()
val type: JavaType = mapper.typeFactory
.constructParametricType(Generic::class.java, Int::class.java)
val instance: Generic<Int> = mapper.readValue("""{"t":32}""", type)
}

Related

Kotlin: generate a Factory by class

We're trying to do some generic processing in kotlin. Basically, for a given class, we want to get the related Builder object. i.a. for any object that extends a GenericObject, we want a Builder of that Object.
interface Builder<T : GenericObject>
object ConcreteBuilder: Builder<ConcreteObject>
We'd need a function that will return ConcreteBuilder from ConcreteObject
Our current implementation is a Map:
val map = mapOf<KClass<out GenericObject>, Builder<out GenericObject>>(
ConcreteObject::class to ConcreteBuilder
)
Then we can get it with:
inline fun <reified T : GenericObject> transform(...): T {
val builder = map[T::class] as Builder<T>
...
However this isn't very nice as:
we need an explicit cast to Builder<T>
the map has no notion of T, a key and a value could be related to different types.
Is there any better way to achieve it?
A wrapper for the map could be:
class BuilderMap {
private val map = mutableMapOf<KClass<out GenericObject>, Builder<out GenericObject>>()
fun <T: GenericObject> put(key: KClass<T>, value: Builder<T>) {
map[key] = value
}
operator fun <T: GenericObject> get(key: KClass<T>): Builder<T> {
return map[key] as Builder<T>
}
}
This hides the ugliness, while not completely removing it.
To use:
val builderMap = BuilderMap()
builderMap.put(ConcreteObject::class, ConcreteBuilder)
builderMap.put(BetonObject::class, BetonBuilder)
// builderMap.put(BetonObject::class, ConcreteBuilder) – will not compile
val builder = builderMap[T::class]

Kotlin. Trying to use reified types to parse Lists and Arrays

I am trying to use reified type when parsing json.
It works perfectly with single json entry, but fails with list.
QUESTIONS:
What am I missing in String.parseList() method?
How come ClassCastException upon .first() despite assignment passed one line earlier?
package qa
import com.fasterxml.jackson.databind.ObjectMapper
import org.slf4j.LoggerFactory
import org.testng.Assert
import org.testng.annotations.Test
class ReifiedParseListTest {
data class User(var name: String = "userName", var age: Int = 0)
val log = LoggerFactory.getLogger(this.javaClass.name)
val objectMapper = ObjectMapper()
val json: String = """[{"name":"Alice","age":1},{"name":"Bob","age":2}]"""
val expected: String = "[User(name=Alice, age=1), User(name=Bob, age=2)]"
inline fun <reified V> String.parseList(): List<V> = objectMapper
.readValue(this, Array<V>::class.java).toList()
#Test
fun checkParseList_OK() {
val actual: List<User> = objectMapper
.readValue(json, Array<User>::class.java).toList()
log.info("actual.first() is of type: {}", actual.first().javaClass)
Assert.assertEquals(actual.toString(), expected)
}
#Test
fun checkParseListReified_FAILS() {
val actual: List<User> = json.parseList<User>()
Assert.assertEquals(actual.toString(), expected)
// java.lang.AssertionError:
// Expected :[User(name=Alice, age=1), User(name=Bob, age=2)]
// Actual :[{name=Alice, age=1}, {name=Bob, age=2}]
}
#Test
fun checkParseListReifiedClassCast_FAILS() {
val actual: List<User> = json.parseList<User>()
log.info("actual.first() is of type: {}", actual.first().javaClass)
// java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to qa.ReifiedParseListTest$User
}
}
In this case, reified helps to propagate the type's class, but there's still type erasure.
To avoid that, you can use something like JavaType:
inline fun <reified V> String.parseList(): List<V> {
return objectMapper.readValue(this, objectMapper.getTypeFactory()
.constructCollectionType(List::class.java, V::class.java))
}
Note that without reified we wouldn't be able to use V::class.java
Now to answer your second question, how come that although val actual is List<User>, you get ClassCastException - the answer is again type erasure, with some obfuscation of platform types.
If you look at what this function returns (it's your function without asList() call:
inline fun <reified V> String.parseList() =
objectMapper.readValue(this, Array<V>::class.java)
You'll notice it returns Array<???>!, which is Kotlin's way of saying "it's something from Java, I hope it will work, but I can't promise". Now by calling toList() this relaxes the compiler, saying "yeah, in the end we return a Kotlin type, it will be alright". But that's a false promise, actually.
What you get is Array<Any> filled with LinkedHashMap, which of course fail when they're being cast to User based on a false promise we've given the compiler.
i finally end up with yet another solution, that seems to handle both single entities and lists
inline fun <reified V> String.parse(): V = objectMapper.readValue(this, object : TypeReference<V>() {})
#Test
fun checkParseSingle() {
val jsonSingle: String = """{"name":"Carol","age":3}"""
val expectedSingle: String = "User(name=Carol, age=3)"
val actual: User = jsonSingle.parse<User>()
Assert.assertEquals(actual.toString(), expectedSingle)
}
#Test
fun checkParseList() {
val jsonList: String = """[{"name":"Alice","age":1},{"name":"Bob","age":2}]"""
val expectedList: String = "[User(name=Alice, age=1), User(name=Bob, age=2)]"
val actual: List<User> = jsonList.parse<List<User>>()
Assert.assertEquals(actual.toString(), expectedList)
}
It fails because of Array<V>::class.java always returning class of Array<Any>. You can see it by executing the following code:
printReifiedArr<String>() // prints `class [Ljava.lang.Object;`
inline fun <reified V> printReifiedArr() {
println(Array<V>::class.java)
}
Your function can be fixed by replacing Array<V>::class.java with a manual array class obtaining:
inline fun <reified V> String.parseList(): List<V> = objectMapper
.readValue(this, Class.forName("[L${V::class.java.name};") as Class<Array<V>>).toList()
Note: this approach uses boxed version of primitives array, other approaches can be found here.
You need to capture generic type which T:class.java won't give. But following will work for any generic type
inline fun <reified T> jacksonTypeRef(): TypeReference<T> = object: TypeReference<T>() {}
inline fun <reified T : Any> String.parseJson(): T {
return objectMapper.readValue(this, jacksonTypeRef<T>())
}

Kotlin unable to access delegated method via reflection: "java.lang.NoSuchMethodException"

I'm trying to understand how reflection with works with delegation and I've come up with a toy example.
class Foo(val m: MutableList<Any>) : MutableList<Any> by m{
}
fun fooAdd(f: Foo) {
val a = f::class.java.getMethod("add").invoke(f, 20);
println(a)
}
fun main(args: Array<String>) {
fooAdd(Foo(mutableListOf()))
}
This gives me an error:
Exception in thread "main" java.lang.NoSuchMethodException: Foo.add()
I'm not sure I understand why it's happening, seeing as add() is delegated to Foo from MutableList if I understand correctly.
How do I fix this error? Also, is there a library one ought to use for such a use-case?
Class#getMethod accepts two parameters:
name of a method.
Parameter types (vararg of Class<?>es).
MutableList has no add method without parameters so you're getting java.lang.NoSuchMethodException.
You meant to get method like this:
clazz.java.getMethod("add", Any::class.java)
Full listing:
fun main() {
val list = mutableListOf<Int>()
val clazz = MutableList::class
val method = clazz.java.getMethod("add", Any::class.java)
method.invoke(list, 10)
println(list)
}
Output:
[10]

Kotlin: objectmapper.readValue() with TypeReference<HashMap<String, String>> cannot infer parameter

I would like to deserialize a json to Map with objectmapper with the following code:
fun onMessage(topic: String, message: MqttMessage) {
val typeRef = object : TypeReference<HashMap<String, String>>() {}
val msg = objectMapper.readValue(message.payload, typeRef)
...
}
Compiler says it connot infer parameter T in fun <T : Any!> readValue (src: ByteArray!, valueTypeRef: (TypeReference<Any!>..TypeReference<*>?)): T!
Is there any solution to this problem whitout extending a HashMap with my custom class like this:
class MyHashMap : HashMap<String, String>()
...
fun onMessage(topic: String, message: MqttMessage) {
val msg = objectMapper.readValue(message.payload, MyHashMap::class.java)
...
}
The issue, really, is in Jackson's API here. Here's how the readValue method is declared:
public <T> T readValue(String content, TypeReference valueTypeRef)
They are using the raw type of TypeReference for some reason, even though they could easily take a TypeReference<T> as their parameter. If they did, you code would work as is, as Kotlin could then infer the T generic type parameter of the function, and therefore know its return type.
You can work around this issue a couple different ways, however, by being explicit with your types.
Either by providing the generic type parameter for the function, and having the type of the msg variable inferred:
val typeRef: TypeReference<Map<K, V>> = object : TypeReference<Map<K, V>>() {}
val msg = objectMapper.readValue<HashMap<String, String>>(message.payload, typeRef)
Or alternatively, by explicitly typing your variable, and having the function's type parameter inferred:
val msg: HashMap<String, String> = objectMapper.readValue(message.payload, typeRef)
One possible way:
inline fun <reified T> ObjectMapper.readValue(s: String): T = this.readValue(s, object : TypeReference<T>() {})
val msg: Map<String,String> = objectMapper.readValue(message.payload)
You can use an object expression to pass an anonymous implementation of TypeReference:
objectMapper.readValue(message.payload, object: TypeReference<HashMap<String, String>>() {})
Notice the object: keyword before the TypeReference parameter.

Declaration-site variance may cause ClassCastException

Kotlin introduces Declaration-site variance described at here.
The out/in keywords for generic parameters may cause ClassCastException in some case. My program is shown below.
fun main(args: Array<String>) {
var l: List<String> = mutableListOf("string")
demo(l)
println("======")
for (s in l) {
println(s)
}
}
fun demo(strs: List<String>) {
val objects: List<Any> = strs // This is OK, since T is an out-parameter
if (objects is MutableList) {
val obs: MutableList<Any> = objects as MutableList<Any>
obs.add(TextView())
}
}
Output:
Exception in thread "main" java.lang.ClassCastException: com.kotlin.demo.clzz.TextView cannot be cast to java.lang.String
at com.kotlin.demo.clzz.Declaration_Site_VarianceKt.main(Declaration-Site-Variance.kt:14)
======
adn
Is the way to use out/in keywords a recommended practice? and Why?
Your code can be compiled without any warnings, this is because declaration-site variance only available in Kotlin.
This is in contrast with Java's use-site variance where wildcards in the type usages make the types covariant.
For example 2 Soruce interfaces use declaration-site variance in Kotlin:
interface Source<out T>
interface Source<in T>
Both of the two Source interfaces will be generated into the same source code in Java as below:
// v---`T extends Object` rather than `? extends T`
public interface Source<T>{ /**/ }
This is because wildcard ? is used as a type argument rather than a type parameter in Java.
The T in Source<T> is a type parameter and the ? extends String in Source<? extends String> is a type argument.
So if you use type projections to make the objects force to a List<out Any>, then the compiler will reports an UNCHECKED_CAST warning , for example:
fun demo(strs: List<String>) {
// v--- makes it explicitly by using out type proejction
val objects: List<out Any> = strs
if (objects is MutableList) {
// v--- an UNCHECKED_CAST warning reported
val obs: MutableList<Any> = objects as MutableList<Any>
obs.add(TextView())
}
}
In other words, you can't assign a List<out Any> to a MutableList<Any>. Otherwise, you will get a compilation error. for example:
fun demo(strs: List<String>) {
val objects: List<out Any> = strs
if (objects is MutableList) {
// v--- ? extends Object
//ERROR: can't assign MutableList<out Any> to Mutable<Any>
// v ^--- Object
val obs: MutableList<Any> = objects
obs.add(TextView())
}
}
IF you assign the objects to a MutableList<out Any> variable, you'll found that you can't adding anything, since you can't create Nothing in Kotlin at all. for example:
fun demo(strs: List<String>) {
val objects: List<out Any> = strs
if (objects is MutableList) {
// v--- down-casting to `MutableList<out Any>`
val obs: MutableList<out Any> = objects
// v---ERROR: can't be instantiated
obs.add(Nothing())
}
}
Q: Is the way to use out/in keywords a recommended practice?
Java has described how to use a wildcard and it also applies in Kotlin.
An "in" Variable, note "in" in here is ? extends T and it is same with Kotlin out variance:
An "in" variable serves up data to the code. Imagine a copy method with two arguments: copy(src, dest). The src argument provides the data to be copied, so it is the "in" parameter.
An "out" Variable, note "out" in here is ? super T and it is same with Kotlin in variance:
An "out" variable holds data for use elsewhere. In the copy example, copy(src, dest), the dest argument accepts data, so it is the "out" parameter.