polymorphic kotlinx serialization fails on object after obfuscation - proguard

i have a polymorphic type that is implemented by objects and classes.
sealed interface Base
#Serializable
#SerialName("Sub")
class Sub(...) : Base
#Serializable
#SerialName("Obj")
object Obj : Base
i use this type with kotlinx.serialization
polymorphic(Base::class) {
subclass(Sub::class)
subclass(Obj::class)
}
this runs when there is no obfuscation, but when obfuscation is turned on, i get:
Serializer for class 'Obj' is not found. Mark the class as #Serializable or provide the serializer explicitly.
my proguard configuration regarding kotlinx.serialization is
-keepclassmembers class kotlinx.serialization.json.** {
*** Companion;
}
-keepclasseswithmembers class kotlinx.serialization.json.** {
kotlinx.serialization.KSerializer serializer(...);
}
-keepclasseswithmembers class .** {
kotlinx.serialization.KSerializer serializer(...);
}
-keep,includedescriptorclasses class my.package.**$$serializer { *; }

The problem is the generated serializer is different for objects and classes.
while in classes it is Sub$serializer in objects it is Obj$defaultSerializer$delegate, which slips through the Proguard rules.
While a good solution would be changing the Proguard rules to catch this case,
a usefull hack is to pass the serializer directly, instead of letting the polymorphic builder find it by the class.
polymorphic(Base::class) {
..
subclass(Obj.serializer())
}

Related

Kotlinx Serialization for a generic sealed class (like an api result)

her is my sealed class
#Serializable
sealed class ApiResponse {
#Serializable
#SerialName("ApiResponse.Success")
data class Success<out T : Any>(val value: T) : ApiResponse()
#Serializable
#SerialName("ApiResponse.Failure")
object Failure : ApiResponse()
#Serializable
#SerialName("ApiResponse.InFlight")
object InFlight : ApiResponse()
}
I am getting this error when I call it
Can't locate argument-less serializer for class Success. For generic
classes, such as lists, please provide serializer explicitly.
I know I am missing something. Is there an easy way to serialize this? Also what is the value of inflight (saw this in an example, but I see tremendous value - I am using ktor for client and server)

How to create compile-time constant in Kotlin from enum?

I have an annotation that requires defaultValue to be compile-time constant. I take defaultValue from enum below:
enum class RaceType {
MARATHON,
SPRINT;
companion object {
fun apply(type: RaceType): RaceDto {
return when (type) {
MARATHON -> MarathonDto()
SPRINT -> SprintDto()
}
}
}
}
My dtos are the following:
interface RaceDto {
}
data class MarathonDto: RaceDto
data class SprintDto: RaceDto
when I use annotation #QraphQLArgument(defaultValue = RaceType.SPRINT.name) Kotlin requires RaceType.SPRINT.name to be compile-time constant.
Annotation implementation itself:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.PARAMETER})
public #interface GraphQLArgument {
String NONE = "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
String NULL = "\n\t\t\n\t\t\n\ue000\ue001\ue002\ue003\n\t\t\t\t\n";
String name();
String description() default "";
String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
Class<? extends DefaultValueProvider> defaultValueProvider() default JsonDefaultValueProvider.class;
}
I looked through similar questions but don't see a way how it can be resolved. I also found article related to the topic but nothing worked so far.
Side note: I cannot change annotation since it is from the library and I cannot change the library as well.
To summarize, is there a way to make from enum compile-time constant in Kotlin to use in an annotation?
is there a way to make from enum compile-time constant in Kotlin to use in an annotation?
No, because formally enums aren't compile-time constants in Java.
However please consider the sealed classes:
sealed class RaceType {
object MARATHON: RaceType() {
const val name = "MARATHON" // copy-paste is required here until https://youtrack.jetbrains.com/issue/KT-16304
}
object SPRINT: RaceType()
companion object {
fun apply(type: RaceType): RaceDto {
return when (type) { // the check is in compile time, because of sealed class
MARATHON -> MarathonDto()
SPRINT -> SprintDto()
}
}
}
}
A little part of copy-paste is still required. Please vote on kotlin compiler bug or follow this thread.
However, as I understand, this doesn't solve your issue with #QraphQLArgument(defaultValue = RaceType.SPRINT.name) unfortunately, because the name of class is not the same with value. In the other words, with sealed classes you need to write code to convert input strings to them.

How to keep internal var in companion object

I have the following class definition:
class SomeObject {
companion object {
internal val instance = SomeObject()
}
}
This class is self sustaining, and is just listening to events.
My problem is that proguard removes the "instance" field.
What do I write in my proguard file to keep the instance field (it can be obfuscated, but i want to keep the field from being deemed as "unused code")
The proguard rule should look like this:
-keepclassmembers class com.your.package.path.SomeObject {
public static ** Companion;
}
This way you can specify classes of which the companion object should not be removed (including the property).

create nested class (kt) for existing class (java), extensions

does Kotlin compiler allow extending classes with inner and/or nested classes and if so what's the correct syntax?
in EnclosingClass.java
class EnclosingJavaClass {
class NestedJavaClass1 {}
class NestedJavaClass2 {}
// ...
}
this is what i'm unsure of below,
in EnclosingClassExtensions.kt
class EnclosingJavaClass.NestedKotlinClass {
// ...
}
assuming that EnclosingClass.java cannot be modified (eg: library code), can i declare somehow a EnclosingClass.SomeNewKotlinClass ?

Class delegation in Kotlin

Trait delegation is described in docs, and there are no questions. But what about class delegation?
class FrameWorkClass // Third party class we cannot modify
class MyDerivedFrameWorkClass(c:FrameWorkClass) : FrameWorkClass by c
What is the best way to achieve this without modifying FrameWorkClass? Obviously we are unable to make it implement our interface.
You can only delegate an interface to a variable that implements that interface. You cannot delegate directly to another class. As #Damian was pointing out; basically is simplified as:
interface Framework {}
class FrameWorkImpl: Framework {}
class MyDerivedFrameWorkClass(val fw: FrameWorkImpl) : Framework by fw {}
Or you can generically say:
class MyDerivedFrameWorkClass(val fw: Framework) : Framework by fw {}
to accept any implementation of the Framework interface and delegate to it, so FrameworkImpl and FrameworkHappyDays both work if they implement the Framework interface.
This is updated to current Kotlin, where trait has been renamed to interface
At the moment only traits can be delegated like this. If you have a class that you would like to delegate, I would simply take that class and extract a trait(interface) including all the methods you are interested in. Here is an example based on your code.
Imagine you have these classes:
class FrameWorkClass {
fun foo() {}
fun boo() {}
}
class MyDerivedFrameWorkClass(c:FrameWorkClass) : FrameWorkClass by c
It does not compile right? Let's say you are interested in foo()
trait FrameWorkTrait {
fun foo() {}
}
class FrameWorkClassImpl: FrameWorkTrait {
override fun foo() {}
fun boo() {}
}
class MyDerivedFrameWorkClass(c:FrameWorkTrait) : FrameWorkTrait by c
At least this is how I would do it.