How to deserialize json to generic type in Kotlin using Jackson/kotlinx.serialization - kotlin

I have a generic class class MyClass<T> : MyInterface<T> and I want to deserialize a json to generic type T. I tried using Jackson and kotlinx.serialization libraries to deserialize json but I get following error
cannot use T as reified type parameter. Use class instead.
My understanding of why this is happening is because both Jackson and kotlinx deserialize function expect reified T but in my class there is no way to know the type of T at compile time. Is my understanding of this error correct? Is there any way to resolve this error?
My code snippet
class MyClass<T> : MyInterface<T>{
.... <some code> ...
fun readFromJson(json: String){
val obj = jacksonObjectMapper().readValue<T>(json)
// same error if I use kotlinx Json.decodeFromString<T>(json)
...
}
.... <some code> ...
}

My understanding of why this is happening is because both Jackson and kotlinx deserialize function expect reified T but in my class there is no way to know the type of T at compile time. Is my understanding of this error correct?
Correct.
Is there any way to resolve this error?
It depends on what you're trying to do with the T in question. The best would be to lift readFromJson() out of this class, to a place where T can actually be reified.
If you really do need this function to be present in your class (e.g. you need to access some internal state or something), then you'll have to pass a KClass<T>/Class<T> (for Jackson) or a DeserializationStrategy<T> (for Kotlinx serialization) to the constructor of your class, so that you can use the non-reified overloads of readValue() or decodeFromString() which take this extra info as parameter.

Related

Kotlin: Generic types in Kotlin

To get the class definition to be used for example for json deserialization the following can be used in Kotlin:
Map::class.java
A example usage is the following:
val map = mapper.readValue(json, Map::class.java)
But now how to have the generic type definition?
Something like this does not compile:
val map = mapper.readValue(decodedString, Map<String, String>::class.java)
So my question is: What is the generic equivalent to *::class.java
Class<T> (in Java) or KClass<T> (in Kotlin) can only represent classes, not all types. If the API you're using only uses Class<T> or KClass<T>, it simply doesn't support generic types (at least in those functions).
Instead, KType (or Type in Java) is the proper type to use to represent the complete type information including generics. You could use it this way:
val myMapType: KType = typeOf<Map<String,String>>()
Unfortunately, KType doesn't have a type parameter (it's not KType<T>), and that makes it impossible to use for compile-time type checking: you can't have the equivalent of fun deserialize(Input, KClass<T>): T using KType instead of KClass, because you can't define the T for the return type by using only a KType argument.
There are several tricks to work around this:
In both Java and Kotlin, one of the ways is to get this information through inheritance by providing a generic superclass and inheriting from it.
In general, serialization APIs (especially the deserializing part) provide workarounds using this, such as Jackson's TypeReference or Gson's TypeToken. It's basically their version of Type but with a type parameter to have some compile-time type safety.
In Kotlin, there is sometimes another way depending on the situation: making use of reified type parameters. Using inline functions, the compiler can know more information at compile time about the type parameters by replacing them with the actual inferred type at the call site when inlining the function's body. This allows things like T::class in the inline function's body. This is how you can get functions like typeOf to get a KType.
Some Kotlin-specific APIs of deserialization libraries use inline functions to remove the hassle from the user, and get type information directly. This is what jackson-module-kotlin does by providing an inline readValue extension without a Class argument, which reifies the type parameter to get the target type information

How can I encode a typed class with Kotlinx Serialization?

I'd like to encode a given class of type T: EventData with Kotlinx Serialization encodeToString.
This is my code:
class EventDispatcher<T: EventData>(
val pubSubTemplate: PubSubTemplate
) {
/**
* Dispatch an event to the game engine event manager pipeline
*/
fun dispatchEvent(event: T, initiator: String) {
val eventData: String = Json.encodeToString(event)
}
The compiler tells me:
Cannot use `T` as reified type parameter. Use a class instead
Is there a way to make this still work?
For Json.encodeToString(event) to work, it needs the type information for T. But, this type information is lost at runtime due to the way how generics work in Kotlin/Java.
One way to retain the type information would be by making dispatchEvent an inline function with T as a reified type parameter.
However, this also raises the question how you want to serialize event. You could also use polymorphic serialization of EventData, rather than trying to serialize T. This will include an additional class discriminator in your serialized output (it necessarily has to for polymorphic serialization/deserialization to work).
If you serialize the concrete type T, this class discriminator wouldn't be included, which is questionable; how would whoever will deserialize this know what type it is?
In short, I think you need polymorphic serialization.

Trying to use Kotlin reified type parameters with a library class

I am using JGraphT library from Kotlin and I want to have my graph parametrised. However, the way I was trying to do it does not work, since U is not defined at the compile time and can not be used for reflection. So I get the error message "Cannot use T as a reified type parameter. Use a class instead." As far as I know, reified type parameters can be used for inline functions to resolve this, but I can not see how it could help me here, especially knowing that I can not change the library code.
Any ideas would be appreciated.
class GraphManipulation<T,U> {
val g = DefaultDirectedGraph<T, U>(U::class.java)
...}
The problem is not with the use of DefaultDirectedGraph, but that U is not reified in your GraphManipulation class. Since classes can't have reified class parameters (yet?) you need to take the class as a constructor parameter:
class GraphManipulation<T,U>(private val uClass: Class<U>) {
val g = DefaultDirectedGraph<T, U>(uClass)
}
Where reified can help is to make a helper method
inline fun <T, reified U> GraphManipulation(): GraphManipulation<T,U> = GraphManipulation(U::class.java)

Jackson ignores data class fields that aren't boolean but are of names starting with "is"

I'm using Jackson with Kotlin binding in my project. We have a data class that has a field of type Map<A, B> and is named "isRecommended". When Jackson serializes the data class, this field gets omitted in the resultant JSON string.
A simple test to reproduce the same behavior:
class FooKotlin {
#Test
fun testFoo() {
println(jacksonObjectMapper().writeValueAsString(Foo1(true)))
println(jacksonObjectMapper().writeValueAsString(Foo2(1)))
println(jacksonObjectMapper().writeValueAsString(Foo3("true")))
}
}
data class Foo1(val isFoo: Boolean)
data class Foo2(val isFoo: Int)
data class Foo3(val isFoo: String)
The console prints:
{"foo":true}
{}
{}
When I decompile the Kotlin bytecode, the three classes seem to have almost identical content except for the type of the field. So what is the cause of this behavior of Jackson?
As mentioned by #chrsblck it is related to the jackson-module-kotlin issue #80
On the version 2.10.1 it's not reproducible, although serialized properties names are different (the "is" prefix is not removed):
{"isFoo":true}
{"isFoo":1}
{"isFoo":"true"}
On the earlier versions, the issue can be fixed with a JsonProperty annotation:
data class Foo1(val isFoo: Boolean)
data class Foo2(#get:JsonProperty("foo") val isFoo: Int)
data class Foo3(#get:JsonProperty("foo") val isFoo: String)
{"foo":true}
{"foo":1}
{"foo":"true"}
Technically, naming a non-boolean property "isSomthing" is incorrect and violates JavaBeans specification. Jackson relies on the JavaBeans conventions, thus it gets confused.
If you can avoid such naming, I would advise doing so. Otherwise, you may face the same problems when calling the Foo* classes from Java code.

generics compilation error in kotlin [duplicate]

I have a generically typed class Builder<T> that takes a constructor argument Class<T> so I can keep the type around. This is a class that I use a lot in java code so I don't want to change the signature.
When I try to use the constructor like this:
Builder<List<Number>>(List<Number>::class)
I get an error: "Only classes are allowed on the left hand side of a class literal"
Any way to resolve this?
I can't change the constructor for Builder, too many java classes rely upon it.
I understand the whole type erasure issue, I really just want to make the compiler happy.
Due to generic type erasure List class has a single implementation for all its generic instantiations. You can only get a class corresponding to List<*> type, and thus create only Builder<List<*>>.
That builder instance is suitable for building a list of something. And again due to type erasure what that something is you can decide by yourself with the help of unchecked casts:
Builder(List::class.java) as Builder<List<Number>>
Builder(List::class.java as Class<List<Number>>)
Another approach is to create inline reified helper function:
inline fun <reified T : Any> Builder() = Builder(T::class.java)
and use it the following way:
Builder<List<Number>>()
The solution is to use reified generics in couple with super class tokens.
Please refer to this question for the method explained. Constructors in Kotlin don't support reified generics, but you can use TypeReference described there to write a builder factory function which will retain actual generic parameters at runtime:
inline <reified T: Any> fun builder(): Builder<T> {
val type = object : TypeReference<T>() {}.type
return Builder(type)
}
Then inside Builder you can check if type is ParameterizedType, and if it is, type.actualTypeArguments will contain the actual generic parameters.
For example, builder<List<Number>>() will retain the information about Number at runtime.
The limitation of this approach is that you cannot use non-reified generic as a reified type parameter because the type must be known at compile-time.