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

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.

Related

Kotlin Generics, correct syntax for type parameters

I have the following class, which basically gets a JSON string from AWS, then converts it to an instance of a data class...
class SecretsManager(region: String) {
private val gson = Gson()
private val smClient = AWSSecretsManagerClientBuilder.standard().withRegion(region).build()
fun <T> getSecret(id: String): T {
val req = GetSecretValueRequest().withSecretId(id)
val json = smClient.getSecretValue(req).getSecretString()
return gson.fromJson(json, T::class.java)
}
}
To be used like this...
val myInstance = SecretsManager("eu-west-2").getSecret<MyDataClass>("myId")
Currently, I get an error - Cannot use 'T' as reified type parameter. I can get around this by marking the function as inline and T as reified , but then I can't access the private attributes from within the function.
What's the best way to do this in Kotlin?
You need to add another parameter to the getSecret method, and also need to add an inline reified method for that to work. See the code below
class SecretsManager(region: String) {
private val gson = Gson()
private val smClient = AWSSecretsManagerClientBuilder.standard().withRegion(region).build()
fun <T> getSecret(type: Class<T>, id: String): T {
val req = GetSecretValueRequest().withSecretId(id)
val json = smClient.getSecretValue(req).getSecretString()
return gson.fromJson(json, type)
}
inline fun <reified T> getSecret(id: String): T = getSecret(T::class.java, id)
}

Kotlin How to create dynamic Object

In javascript we can do something like this
function putritanjungsari(data){
console.log(data.name)
}
let data = {
name:"putri",
div:"m4th"
}
putritanjungsari(data)
In kotlin, i'am creating a function that accept an object as parameter then read it's properties later, how to do that in kotlin that targeting JVM?
If I understood your question correct, you are trying to have a variable that associates keys with some value or undefined(null in kt) if none are found. You are searching for a Map
If you don't know what types you want, you can make a map of type Any? So
Map<String, Any?>
Which is also nullable
Map<String, Any>
If you don't want nullables
Your code for example:
fun putritanjungsari(data: Map<String, Any?>){
print(data["name"])
}
val data: Map<String, Any?> =mapOf(
"name" to "putri",
"div" to "m4th"
)
putritanjungsari(data)
Note that you can't add new keys or edit any data here, the default map is immutable. There is MutableMap (which is implemented the same, only it has a method to put new data)
You can apply the property design pattern to solve your problem.
Here is its implementation in Kotlin:
interface DynamicProperty<T> {
fun cast(value: Any?): T
fun default(): T
companion object {
inline fun <reified T> fromDefaultSupplier(crossinline default: () -> T) =
object : DynamicProperty<T> {
override fun cast(value: Any?): T = value as T
override fun default(): T = default()
}
inline operator fun <reified T> invoke(default: T) = fromDefaultSupplier { default }
inline fun <reified T> required() = fromDefaultSupplier<T> {
throw IllegalStateException("DynamicProperty isn't initialized")
}
inline fun <reified T> nullable() = DynamicProperty<T?>(null)
}
}
operator fun <T> DynamicProperty<T>.invoke(value: T) = DynamicPropertyValue(this, value)
data class DynamicPropertyValue<T>(val property: DynamicProperty<T>, val value: T)
class DynamicObject(vararg properties: DynamicPropertyValue<*>) {
private val properties = HashMap<DynamicProperty<*>, Any?>().apply {
properties.forEach { put(it.property, it.value) }
}
operator fun <T> get(property: DynamicProperty<T>) =
if (properties.containsKey(property)) property.cast(properties[property])
else property.default()
operator fun <T> set(property: DynamicProperty<T>, value: T) = properties.put(property, value)
operator fun <T> DynamicProperty<T>.minus(value: T) = set(this, value)
}
fun dynamicObj(init: DynamicObject.() -> Unit) = DynamicObject().apply(init)
You can define your properties these ways:
val NAME = DynamicProperty.required<String>() // throws exceptions on usage before initialization
val DIV = DynamicProperty.nullable<String>() // has nullable type String?
val IS_ENABLED = DynamicProperty(true) // true by default
Now you can use them:
fun printObjName(obj: DynamicObject) {
println(obj[NAME])
}
val data = dynamicObj {
NAME - "putri"
DIV - "m4th"
}
printObjName(data)
// throws exception because name isn't initialized
printObjName(DynamicObject(DIV("m4th"), IS_ENABLED(false)))
Reasons to use DynamicObject instead of Map<String, Any?>:
Type-safety (NAME - 3 and NAME(true) will not compile)
No casting is required on properties usage
You can define what the program should do when a property isn't initialized
Kotlin is statically typed language, so it required a param type to be precisely defined or unambiguously inferred (Groovy, for instance, addresses the case by at least two ways). But for JS interoperability Kotlin offers dynamic type.
Meanwhile, in your particular case you can type data structure to kt's Map and do not argue with strict typing.
You have to use Any and after that, you have to cast your object, like this
private fun putritanjungsari(data : Any){
if(data is Mydata){
var data = data as? Mydata
data.name
}
}
Just for the sake of inspiration. In Kotlin, you can create ad hoc objects:
val adHoc = object {
var x = 1
var y = 2
}
println(adHoc.x + adHoc.y)

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>())
}

How to Deserialize Jackson container with Generics Generically

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)
}

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.