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

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

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 reified issue with parameterized type

Why does the following not work?
It seems that the type Foo is not passed properly, or is this just a well known "issue"?
Foo<T> fails
data class Foo<T>(val a: T)
data class Bar(val b: String)
val objectMapper = ObjectMapper().registerKotlinModule()
val jsonString = "{\"a\": { \"b\": \"str\" }}"
fun main() {
val parseJson = parseJson<Bar>()
}
private inline fun <reified T> parseJson(): T {
val readValue: Foo<T> = objectMapper.readValue(jsonString)
return readValue.a
}
class java.util.LinkedHashMap cannot be cast to class ..Bar
(java.util.LinkedHashMap is in module java.base of loader 'bootstrap';
..Bar is in unnamed module of loader 'app')
The issue in this case is that you're not specifying the class that ObjectMapper should try and map your jsonString to. The matter is further complicated by the Foo type being parametric.
You need to build a JavaType reference to pass to objectMapper.readValue.
private inline fun <reified T> parseJson(): T {
val javaType = objectMapper.typeFactory.constructParametricType(Foo::class.java, T::class.java)
val readValue: Foo<T> = objectMapper.readValue(jsonString, javaType)
return readValue.a
}

Is it possible to make safe inline Optional in Kotlin?

In Kotlin sometimes I have to work with double nullability. For example, I need double nullability, when I want to use T? where T may be a nullable type. There are a few approaches for doing this:
Holder<T>? where Holder is data class Holder<out T>(val element: T) - example1
boolean flag variable - example1
containsKey for Map<K, T?> - example1
The special UNINITIALIZED_VALUE for representing the second kind of null - example1
The last approach has the best performance, but it's also the most error-prone. So I've decided to encapsulate it in inline class Optional<T>:
inline class Optional<out T> #Deprecated(
message = "Not type-safe, use factory method",
replaceWith = ReplaceWith("Optional.of(_value)")
) constructor(private val _value: Any?) {
val value: T?
get() =
#Suppress("UNCHECKED_CAST")
if (isPresent) _value as T
else null
val isPresent: Boolean
get() = _value != NULL
companion object {
#Suppress("DEPRECATION")
fun <T> of(value: T) = Optional<T>(value)
fun <T : Any> ofNullable(value: T?): Optional<T> =
if (value == null) EMPTY
else of(value)
#Suppress("DEPRECATION")
val EMPTY = Optional<Nothing>(NULL)
}
private object NULL
}
inline fun <T> Optional<T>.ifPresent(code: (T) -> Unit) {
#Suppress("UNCHECKED_CAST")
if (isPresent) return code(value as T)
}
inline fun <T> Optional<T>.or(code: () -> T): T {
ifPresent { return it }
return code()
}
The first problem with this Optional is public constructor, which allows creating instances with arguments of not matching type.
The second problem was noticed at testing time. Here is the failed test:
emptyOr { Optional.EMPTY }.value assertEql null
fun <T> emptyOr(other: () -> T): T = Optional.EMPTY.or(other)
Exception:
Exception ClassCastException: Optional$NULL cannot be cast to Optional
at (Optional.kt:42) // emptyOr { Optional.EMPTY }.value assertEql null
If I remove inline modifier from Optional, the test will pass.
Q: Is there any way to fix these problems without removing inline modifier from Optional?
1 Examples include some context. Please read them fully before writing that I added incorrect links.
I implemented exactly the same utility in one of my projects: OptionalValue.kt. My implementation is very similar to yours, it is also an inline/value class, so it should be cpu/memory efficient and it passes all tests I throw at it.
Regarding your first question: about a public constructor. There is an annotation specifically for this case: #PublishedApi. I tried to reproduce ClassCastException from your example, but it worked for me without problems, so I believe it was a bug in Kotlin itself (?).
Also, to answer the question why do we need double nullability, I explained my point here

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)

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