Problem
I have a data class in commonMain (called Person) that I would like to access from jvmMain as type java.io.Serializable.
I have a solution, which is shown below, but I was wondering if this is the best approach. I also found that the library kotlinx.serialization exists, but I'm not sure if it can be a solution.
Current code and solution: expected and actual types
This code works fine, although the required DummyInterface may be a bit useless.
// CommonMain
expect interface Serializable
data class Person(val name: String) : Serializable
// jsMain
interface DummyInterface
actual typealias Serializable = DummyInterface
//jvmMain
actual typealias Serializable = java.io.Serializable
fun main(args: Array<String>) {
val p1: java.io.Serializable = Person("abc")
println(p1)
}
Tried and failed code with kotlinx.serialization
// gradle.kotlin.kts
plugins {
application
kotlin("multiplatform") version "1.4.32"
kotlin("plugin.serialization") version "1.4.32"
}
repositories {
mavenCentral()
}
kotlin {
jvm {
compilations.all {
kotlinOptions.jvmTarget = "11"
}
withJava()
}
js(IR) {
binaries.executable()
browser {}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
}
}
val jvmMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
}
}
}
}
// commonMain/kotlin/Person.kt
import kotlinx.serialization.*
#Serializable
data class Person(val name: String)
// jvmMain/kotlin/main.kt
fun main(args: Array<String>) {
// Fails with: "Type mismatch: inferred type is Person but Serializable was expected"
val p1: java.io.Serializable = Person("abc")
println(p1)
}
I know why it fails with a type mismatch, but I would hoping that the kotlinx.serialization plugin would magically add the interface java.io.Serializable to the Person data class.
Question
Is solution with expected and actual types, the best solution for this problem?
Would kotlinx.serialization also be able to provide a solution? If so, what code should I alter?
kotlinx.serialization wasn't exactly developed as an java.io.Serializable abstraction or something. It's a purely kotlin serialization library, for serializing/deserializing JSON objects.
Yes, your first approach is a proper solution I'd say.
There is a similar implementation for Parcelize, you could check out moko-parcelize, it's doing the same thing.
Related
Suppose we have a structure like this:
data class MyClass(
val inner: MyInnerClass
) {
data class MyInnerClass(
val foo: String
)
}
How can I get the relative class name MyClass.MyInnerClass? I already have a KType of that class. And if I look at this with the debugger I can find the name:
I could not find out how to access this with code.
This is possible using the kotlin-reflect artifact as a dependency.
https://kotlinlang.org/docs/reflection.html#jvm-dependency
In my Maven project I add a new dependency:
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>1.7.0</version>
</dependency>
</dependencies>
I didn't use Gradle for this, but I think you'd use
dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect:1.7.0")
}
to include it.
I made a data class similar to yours in a package named innerclass:
package innerclass
data class MyClass(
val inner: MyInnerClass
) {
data class MyInnerClass(val foo:String)
data class AnotherInnerClass(val bar:String)
}
Then my Main.kt looks like this:
import innerclass.MyClass
import kotlin.reflect.KClass
fun main(args: Array<String>) {
println("Using class type as reference: ${MyClass::class.qualifiedName}")
printRelativeClassNames(MyClass::class.nestedClasses)
val test = MyClass(MyClass.MyInnerClass("foo"))
println("\nUsing instance as reference: ${test::class.qualifiedName}")
printRelativeClassNames(test::class.nestedClasses)
}
private val KClass<*>.packageFqName: String?
get() {
return java.`package`.name
}
private val KClass<*>.relativeClassName: String?
get() {
return qualifiedName?.removePrefix("${packageFqName}.")
}
fun printRelativeClassNames(nestedClasses: Collection<KClass<*>>) {
nestedClasses.forEach {
println("Actual Kotlin class: $it")
println(it.relativeClassName)
}
}
When I run the program, it outputs:
Using class type as reference: innerclass.MyClass
Actual Kotlin class: class innerclass.MyClass$AnotherInnerClass
MyClass.AnotherInnerClass
Actual Kotlin class: class innerclass.MyClass$MyInnerClass
MyClass.MyInnerClass
Using instance as reference: innerclass.MyClass
Actual Kotlin class: class innerclass.MyClass$AnotherInnerClass
MyClass.AnotherInnerClass
Actual Kotlin class: class innerclass.MyClass$MyInnerClass
MyClass.MyInnerClass
Using reflection (and a little string manipulation) I can get the Kotlin class as well as print out the relative name of the class.
Let me know if you hit any issues.
I am trying to serialize my base class that is implementing two sealed interfaces. I have tried multiple approaches, yet i always get the error :
caused by: kotlinx.serialization.SerializationException: Class 'PayloadFromBuilder' is not registered for polymorphic serialization in the scope of 'Payload'.
Mark the base class as 'sealed' or register the serializer explicitly.
I was following mostly this guide Kotlinx/polymorphism and checked some similar questions here.
My code:
sealed inteface MyClass {
dataetc
}
#Serializable
private class DefaultMyClass(dataetc): MyClass
fun MyClass(dataetc): MyClass = DefaultMyClass
Sealed interface MyClassBuilder {
fun dataetc(value: ByteArray)
fun dataetc(value: ByteArray)
fun dataetc(value: ByteArray?)
}
#PublishedApi
#Serializable
#SerialName("payload")
internal class MyClassFromBuilder: MyClassBuilder, MyClass {
}
//Serialization
val module = SerializersModule {
polymorphic(MyClass::class) {
subclass(MyClassFromBuilder::class, MyClassFromBuilder.serializer())
default { MyClassFromBuilder.serializer() }
}
polymorphic(MyClassBuilder::class) {
subclass(MyClassFromBuilder::class, MyClassFromBuilder.serializer())
default { MyClassFromBuilder.serializer() }
}
}
val ConfiguredProtoBuf = ProtoBuf { serializersModule = module }
#ExperimentalSerializationApi
internal inline fun <reified T> ProtoBuf.encodeToMessage(value: T): Message =
Message(encodeToByteArray(value))
From what i have seen i think i am very close to the solution yet i am missing something, since my example is very generic if you need more info let me know, thank you in advance.
Note: In my several tries i have tried to annotate both sealed intefaces with #Polymorphic but i am not sure if it changed anything.
Note 2: My code breaks when i am calling the encodeToMessage fun
So i messed big time, turns out i was not using my ConfiguredProtoBuf when i was calling my encodeToMessage
I am trying to make a simple server which gives serialized List in JSON. The List to be serialized is the example in the official blog post's Polymorphic serialization section.
But with the ktor's serialization feature, I get the following exception.
21:53:25.536 [nioEventLoopGroup-4-1] ERROR ktor.application - Unhandled: GET - /
java.lang.IllegalStateException: Serializing collections of different element types is not yet supported. Selected serializers: [DirectMessage, BroadcastMessage]
at io.ktor.serialization.SerializerLookupKt.elementSerializer(SerializerLookup.kt:71)
Since sealed class is a key feature to choose Kotlin, I really wonder why this is not supported.
Are there any good reasons for ktor-serialization not supporting this? Or should I post an issue for removing this check from SerializerLookup.kt?
I made this code by choosing New Project > Kotlin > Application in IntelliJ. The modified code is shown below.
My server.kt:
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.serialization.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import kotlinx.serialization.Serializable
#Serializable
sealed class Message {
abstract val content: String
}
#Serializable
data class BroadcastMessage(override val content: String) : Message()
#Serializable
data class DirectMessage(override val content: String, val recipient: String) : Message()
val data: List<Message> = listOf(
DirectMessage("Hey, Joe!", "Joe"),
BroadcastMessage("Hey, all!")
)
fun main() {
embeddedServer(Netty, port = 8080, host = "127.0.0.1") {
install(ContentNegotiation) {
json()
}
routing {
get("/") {
call.respond(data)
}
}
}.start(wait = true)
}
My build.gradle.kts:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.4.10"
application
kotlin("plugin.serialization") version "1.4.10"
}
group = "com.example.ktor.serialization"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
jcenter()
maven {
url = uri("https://dl.bintray.com/kotlin/ktor")
}
maven {
url = uri("https://dl.bintray.com/kotlin/kotlinx")
}
}
dependencies {
testImplementation(kotlin("test-junit5"))
implementation("io.ktor:ktor-server-netty:1.4.1")
implementation("io.ktor:ktor-html-builder:1.4.1")
implementation("io.ktor:ktor-serialization:1.4.1")
implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.7.2")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0")
implementation("ch.qos.logback:logback-classic:1.2.3")
}
tasks.withType<KotlinCompile>() {
kotlinOptions.jvmTarget = "11"
}
application {
mainClassName = "ServerKt"
}
As said in the stacktrace this is not supported yet., so it might come someday.
However, a workaround is still possible for such a case.
The issue is from Ktor, not Kotlinx Serialization.
So, you can serialize your data as JSON, and then send them as a response, like here:
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) { json() }
routing {
get("/") {
val data: List<Message> = listOf(
DirectMessage("Hey, Joe!", "Joe"),
BroadcastMessage("Hey, all!")
)
val string = Json.encodeToString(data)
call.respondText(string, contentType = ContentType.Application.Json)
}
}
}
#Serializable
sealed class Message {
abstract val content: String
}
#Serializable
data class BroadcastMessage(override val content: String) : Message()
#Serializable
data class DirectMessage(override val content: String, val recipient: String) : Message()
The reason is that we don't have the particular type information and can only analyze instance classes in runtime. Analyzing and runtime type intersection is not an easy task and for sure it will be very inefficient that is unacceptable on server-side.
Using typeOf could potentially help, but we haven't analyzed the performance impact of such a change (including allocation profile). The other reason is that we didn't know about typeOf (it didn't exist) and call.respond has been designed without it so this change will for sure be a breaking change.
Is there a way to implement something similar to Lombok's annotation #slf4j with annotation class in Kotlin?
Right now I have an extension function that instantiates a Logger Factory for me, and I must create these variables in each of my classes just like the example below:
#RestController
#RequestMapping("/api/v1/sample")
class SampleController() {
private val log = logger()
#GetMapping
fun show(): String {
log.info("SOME LOGGING MESSAGE")
return "OK"
}
}
inline fun <reified T> T.logger(): Logger {
if (T::class.isCompanion) {
return LoggerFactory.getLogger(T::class.java.enclosingClass)
}
return LoggerFactory.getLogger(T::class.java)
}
What I want to achieve is something like:
#Logger
#RestController
#RequestMapping("/api/v1/sample")
class SampleController() {
#GetMapping
fun show(): String {
log.info("SOME LOGGING MESSAGE")
return "OK"
}
}
made this pretty thing yesterday
#Target(AnnotationTarget.CLASS)
#Retention(AnnotationRetention.RUNTIME)
annotation class Log {
companion object {
inline var <reified T> T.log: Logger
get() = LoggerFactory.getLogger(T::class.java)
set(value) {}
}
}
edit:
Don't use this mess above. use
companion object {
private val log: Logger = LoggerFactory.getLogger(this::class.java)
}
see Idiomatic way of logging in Kotlin
Turning Danny Lagrouw's comment into an answer:
You can get a similar to #Slf4j low-boilerplate solution that does not require you to specify the class manually by using Micro Utils' kotlin-logging
import mu.KotlinLogging
private val log = KotlinLogging.logger {}
class LoggingDemoClass() {
fun logSometing() {
log.info("Logging like a pro!")
}
}
Caveat: I've only just started using it but and haven't investigated any of the kotlin sugar it puts on top of Slf4j but so far it seems to handle well enough as a drop-in replacement for #Slf4j in kotlin code.
I have a base abstract module that provides some dependencies for Retrofit/OkHttp related objects. This module is missing some dependencies, and I've created another module that extends this module to provide these missing dependencies.
Here's what the base abstract module looks like:
#Module
abstract class BaseApiModule {
#[Provides Singleton]
fun provideOkHttpClient(interceptors: List<Interceptor>) : OkHttpClient {
return OkHttpClient.Builder()
.apply {
for (interceptor in interceptors) {
addInterceptor(interceptor)
}
}
.build()
}
#[Provides Singleton]
fun provideRetrofit(
baseUrl: String,
okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(MoshiConverterFactory.create())
.baseUrl(baseUrl)
.client(okHttpClient)
.build()
}
}
Here's what the module that implements this abstract module looks like:
#Module
object ApiModule : BaseApiModule() {
#[JvmStatic Provides Singleton]
fun provideBaseUrl() = "https://api.thecatapi.com/v1/"
#[JvmStatic Provides Singleton]
fun provideInterceptors() = listOf<Interceptor>(HttpLoggingInterceptor())
#[JvmStatic Provides Singleton]
fun provideApi(retrofit: Retrofit) = retrofit.create(Api::class.java)
}
I'm trying to establish the modules in a way where:
- BaseApiModule is missing some dependencies
- ApiModule provides these dependencies
This implementation isn't working for me: whenever I build my code, I get the following error: error: [Dagger/MissingBinding] #com.example.api.Interceptors java.util.List<? extends okhttp3.Interceptor> cannot be provided without an #Provides-annotated method. If I remove the interceptors dependencies completely, my code builds just fine. I'd like an explanation on:
Why does this module setup break and why does it build when I remove the dependencies on a List<Interceptor>
What's the standard way in dagger to have a module supply missing dependencies to another module?
I fixed this by making ApiModule not a singleton object and removing all JvmStatic annotations from it.