I am trying to replace Gson library by kotlin serialization to handle JSON serialization/deserialization.
I am facing some issues to deserialize generic objects I have setup a simple example of what I am trying to achieve:
#Serializable
data class ContentMessageDto<T>(
val method: String,
val content: T
)
#Serializable
private data class DummyObjectNonNullProperties(
val value: Int,
#SerialName("aaa") val someProp: String,
val bbb: Boolean,
val ccc: Double
)
interface MyParser {
fun <T> parseContentMessage(
json: String
): ContentMessageDto<T>
}
class MyParserImpl(private val jsonSerialization: Json) : MyParser {
override fun <T> parseContentMessage(json: String): ContentMessageDto<T> {
return jsonSerialization.decodeFromString<ContentMessageDto<T>>(json)
}
}
fun main() {
println("start processing...")
val jsonToParse = """
{
"method":"get",
"content":{
"value":345,
"aaa": "some string",
"bbb": true,
"ccc": 23.4
}
}""".trimIndent()
val parser:MyParser = MyParserImpl(Json)
val result = parser.parseContentMessage<DummyObjectNonNullProperties>(jsonToParse)
println("result -> $result")
}
But when I run the main method, I get the following error:
Exception in thread "main" java.lang.IllegalStateException: Only KClass supported as classifier, got T
at kotlinx.serialization.internal.Platform_commonKt.kclass(Platform.common.kt:102)
at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:52)
at kotlinx.serialization.SerializersKt.serializer(Unknown Source)
at kotlinx.serialization.SerializersKt__SerializersKt.builtinSerializerOrNull$SerializersKt__SerializersKt(Serializers.kt:79)
at kotlinx.serialization.SerializersKt__SerializersKt.serializerByKTypeImpl$SerializersKt__SerializersKt(Serializers.kt:69)
at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:54)
at kotlinx.serialization.SerializersKt.serializer(Unknown Source)
But I am not sure why. Can someone provide me an explanation and if possible some tips on how I can implement this?
It would have worked if you've simply done:
val result = Json.decodeFromString<ContentMessageDto<DummyObjectNonNullProperties>>(jsonToParse)
But with all this wrapping, type information about T was lost. The problem is that you can't simply use reified generics here, cause inline functions can't be non-final.
Possible workarounds:
Define parseContentMessage as extension function so that it could have inline modifier (and T could be reified):
interface MyParser {
val jsonSerialization: Json
}
inline fun<reified T> MyParser.parseContentMessage(json: String): ContentMessageDto<T> {
return jsonSerialization.decodeFromString(json)
}
class MyParserImpl(override val jsonSerialization: Json) : MyParser
//Usage will be the same
Manually pass serializer for T into parseContentMessage:
interface MyParser {
fun <T> parseContentMessage(json: String, contentSerializer: KSerializer<T>): ContentMessageDto<T>
}
class MyParserImpl(private val jsonSerialization: Json) : MyParser {
override fun <T> parseContentMessage(json: String, contentSerializer: KSerializer<T>): ContentMessageDto<T> {
return jsonSerialization.decodeFromString(ContentMessageDto.serializer(contentSerializer), json)
}
}
//Usage:
val result = parser.parseContentMessage(jsonToParse, DummyObjectNonNullProperties.serializer())
Related
I wrote the following function:
fun <T> myFunction(res: Response, type: T): T {
return res.`as`(type!!::class.java)
}
And I would like to use it in the following way:
fun anotherFunction(): MyClass {
val res = getResponse()
return myFunction(res, MyClass)
}
But I get the following error:
Type mismatch.
Required: MyClass
Found: MyClass.Companion
How can I solve it?
I don't know exactly what the as function does, but I suppose it performs some sort of conversion from one class instance into an instance of some other class?
You seem to be trying to pass a class type as a function argument. You can do it in one of two ways:
fun <T: Any> myFunction(res: Response, type: Class<T>): T {
return res.`as`(type)
}
fun anotherFunction(): MyClass {
val res = getResponse()
return myFunction(res, MyClass::class.java)
}
or
inline fun <reified T: Any> myFunction(res: Response): T {
return res.`as`(T::class.java)
}
fun anotherFunction(): MyClass {
val res = getResponse()
return myFunction(res) // <MyClass> is inferred by anotherFunction return type
}
I'm in a situation where I'm trying to setup some data and then call a service. Each step can fail, so I'm trying to use Arrow's Either to manage this.
But I'm ending up with a lot of nested flatMaps.
The following code snippet illustrates what I'm trying to do:
import arrow.core.Either
import arrow.core.flatMap
typealias ErrorResponse = String
typealias SuccessResponse = String
data class Foo(val userId: Int, val orderId: Int, val otherField: String)
data class User(val userId: Int, val username: String)
data class Order(val orderId: Int, val otherField: String)
interface MyService {
fun doSomething(foo: Foo, user: User, order: Order): Either<ErrorResponse, SuccessResponse> {
return Either.Right("ok")
}
}
fun parseJson(raw: String): Either<ErrorResponse, Foo> = TODO()
fun lookupUser(userId: Int): Either<ErrorResponse, User> = TODO()
fun lookupOrder(orderId: Int): Either<ErrorResponse, Order> = TODO()
fun start(rawData: String, myService: MyService): Either<ErrorResponse, SuccessResponse> {
val foo = parseJson(rawData)
val user = foo.flatMap {
lookupUser(it.userId)
}
//I want to lookupOrder only when foo and lookupUser are successful
val order = user.flatMap {
foo.flatMap { lookupOrder(it.orderId) }
}
//Only when all 3 are successful, call the service
return foo.flatMap { f ->
user.flatMap { u ->
order.flatMap { o ->
myService.doSomething(f, u, o)
}
}
}
}
I'm sure there is a better way to do this. Can someone help me with an idiomatic approach?
You can use the either { } DSL, this is available in a suspend manner or in a non-suspend manner through the either.eager { } builder.
That way you can use suspend fun <E, A> Either<E, A>.bind(): A.
Rewriting your code example:
fun start(rawData: String, myService: MyService): Either<ErrorResponse, SuccessResponse> =
either.eager {
val foo = parseJson(rawData).bind()
val user = lookupUser(foo.userId).bind()
val order = lookupOrder(foo.orderId).bind()
myService.doSomething(foo, user, order).bind()
}
If you run into an Either.Left, then bind() will short-circuit the either.eager block and return with the encountered Either.Left value.
I am trying to implement a generic HttpClient like this one:
interface HttpClient {
fun <T: Any> get(url: String): T?
}
implemented by a class like this:
class HttpClientImpl #Inject constructor(...) : HttpClient {
override fun <T : Any> get(url: String): T? = execute(url)
private inline fun <reified T: Any> execute(url: String): T? {
val request = Request.Builder().url(url).get().build()
client.newCall(request).execute().use {
return it.body?.parseBodySuccess()
}
}
private inline fun <reified T: Any> ResponseBody?.parseBody(): T? {
val type = objectMapper.typeFactory.constructType(T::class.java)
return this?.let { objectMapper.readValue(it.string(), type) }
}
}
Now, I would like to be able to call such GET method in this way:
data class MyEntity(...)
class MyService #Inject constructor(private val client: HttpClient) {
fun performGet(url: String): MyEntity? = client.get<MyEntity>(url)
}
However this is not allowed and the compiler throws an error referring to the line of code
override fun <T : Any> get(endpoint: String): T? = execute(endpoint)
flagging that : Cannot use 'T' as reified type parameter. Use a class instead.
I have been trying to re-write the line as
override inline fun <reified T : Any> get(endpoint: String): T? = execute(endpoint)
however, despite having to make the other two inline functions "non private" the compiler still won't compile because in this last way of writing the overriding function, it says:
Override by a function with reified type parameter
How can I achieve such generic function?
I ended up doing something like this:
interface HttpClient {
fun <T: Any> get(url: String, type: Class<T>): T?
}
implemented as:
class HttpClientImpl #Inject constructor(...) : HttpClient {
override fun <T : Any> get(url: String, type: Class<T>): T? = execute(url, type)
private fun <T: Any> execute(url: String, type: Class<T>): T? {
val request = Request.Builder().url(url).get().build()
client.newCall(request).execute().use {
return it.body?.parseBody(type)
}
}
private fun <T: Any> ResponseBody?.parseBody(type: Class<T>): T? {
val dataType = objectMapper.typeFactory.constructType(type)
return this?.let { objectMapper.readValue(it.string(), dataType) }
}
}
that I can call in this way:
data class MyEntity(...)
class MyService #Inject constructor(private val client: HttpClient) {
fun performGet(url: String): MyEntity? = client.get(url, MyEntity::class.java)
}
I would have preferred to pass the Type directly as an actual type like
client.get<MyEntity>(url)
rather than passing the Class as a parameter, however, just for now it works...
If anyone can suggest a better way of doing this, please let me know.
Updated
As suggested by Pawel, I have created an extra inline extension function to the HttpClient interface
inline fun <reified T:Any> HttpClient.get (url: String) = get(url, T::class.java)
And I'm now able to call the function the way I wanted.
For example, I have JSON
{
"url": "//n.ya.com"
}
In order to deserialize, I define the data class
#Serializable
data class Foo(
#SerialName("url")
val link: String,
)
After deserializing the Foo object has
foo.link with "//n.ya.com"
How can I remove the // during the deserializing, which means foo.link with "n.ya.com"?
You can add custom Serializer for a single property:
#Serializable
data class Foo(
#SerialName("url")
#Serializable(with = FooLinkDeserializer::class)
val link: String,
)
object FooLinkSerializer : KSerializer<String> {
override val descriptor = PrimitiveSerialDescriptor("Foo.link", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): String {
return decoder.decodeString().substringAfter("//")
}
override fun serialize(encoder: Encoder, value: String) {
encoder.encodeString("//$value")
}
}
Or you can intercept JSON transformations using JsonTransformingSerializer:
#Serializable
data class Foo(
#SerialName("url")
#Serializable(with = FooLinkInterceptor::class)
val link: String,
)
object FooLinkInterceptor : JsonTransformingSerializer<String>(String.serializer()) {
override fun transformDeserialize(element: JsonElement): JsonElement {
return JsonPrimitive(element.jsonPrimitive.content.substringAfter("//"))
}
}
I want to get the name of a parameter passed into a lambda expression in Kotlin. In C# I would use an Expression<Func<T, ...>> to get the parameter name, but I'm not sure whether this is possible in Kotlin
import java.util.*
fun main(args: Array<String>) {
val foo = Foo<Model>()
foo.bar { it.age }
// Should print "age"
}
data class Model(val id: UUID, val name: String, val age: Int)
class Foo<T> {
fun bar(expression: (x: T) -> Any) {
println(/*The name of the parameter*/)
}
}
Is this possible in Kotlin?
If you want to pass around properties and print their names, you can do it by using KProperty:
fun main(args: Array<String>) {
val foo = Foo<Model>()
foo.bar(Model::age)
}
class Foo<T> {
fun bar(p: KProperty<*>) {
println(p.name)
}
}