Kotlin / kotlinx.serialization

Kotlin multiplatform / multi-format serialization
Apache License 2.0
5.41k stars 620 forks source link

Generic custom serializer breaks kotlin-kapt compilation #685

Open alexvanyo opened 4 years ago

alexvanyo commented 4 years ago

Describe the bug When using a generic custom serializer (see example below), adding the kotlin-kapt plugin with something that uses kapt (like data binding or Dagger) causes a Kotlin compiler error.

To Reproduce The following generic custom serializer works as expected in an Android project when kotlin-kapt is not in use:

data class Wrapped<T>(
    val value: T
)

open class WrappedSerializer<T>(private val serializer: KSerializer<T>): KSerializer<Wrapped<T>> {
    override val descriptor: SerialDescriptor = serializer.descriptor
    override fun deserialize(decoder: Decoder): Wrapped<T> =
        Wrapped(serializer.deserialize(decoder))
    override fun serialize(encoder: Encoder, obj: Wrapped<T>) =
        serializer.serialize(encoder, obj.value)
}

@Serializable
data class A(
    @Serializable(with = WrappedSerializer::class)
    val value: Wrapped<String>
)

However, adding

apply plugin: 'kotlin-kapt'

and

android {
    dataBinding {
        enabled = true
    }
}

causes building the build to fail with the following compiler exception:

error: incompatible types: Class<WrappedSerializer> cannot be converted to Class<? extends KSerializer<?>> @kotlinx.serialization.Serializable(with = com.alexvanyo.kotlintest.WrappedSerializer.class)

Expected behavior Applying the kotlin-kapt plugin shouldn't cause a compiler error.

As a temporary workaround, specifying the type does allow compilation with kotlin-kapt, but this isn't ideal as it requires a separate class for every serialized type:

class StringWrappedSerializer(serializer: KSerializer<String>): WrappedSerializer<String>(serializer)

@Serializable
data class A(
    @Serializable(with = StringWrappedSerializer::class)
    val value: Wrapped<String>
)

Environment

alexvanyo commented 4 years ago

Just confirmed this still occurs with Kotlin 1.3.71 and a runtime version of 0.20.0. I can also upload a test project if that would be helpful!

teal77 commented 4 years ago

Worked around this by moving all network code into a separate module with no kapt plugin.

alexvanyo commented 4 years ago

Did some more investigation, and I believe this is the same issue as https://youtrack.jetbrains.com/issue/KT-30346, so that would make this firmly a kapt bug.

In https://github.com/JetBrains/kotlin, there's two commits that reference the issue, https://github.com/JetBrains/kotlin/commit/66754e62dadd5ad05921b630ab3d4b2b58a33a83, and https://github.com/JetBrains/kotlin/commit/a0778ad703d7d7ce3cb4532d5954fa32a6045db3, that latter of which reverts the first.

Not sure what the relevant priority is, but I'm going to link this issue on https://youtrack.jetbrains.com/issue/KT-30346.

eugenio1590 commented 3 years ago

I am getting an issue similar to this. I am using databinding and kotlinx.serialization. I have an attribute with different names, so I included the @JsonNames annotation with more that one name.

@Serializable
data class DocMessage(
    val id: Int = 0,
    @JsonNames("type1", "type2")
    val type: Type
)

@Serializable(with =TypeSerializer::class)
enum class Type(val value: String) {
    PDF("pdfDoc"), IMAGE("imageDoc"), UNKNOWN("unknown");
}

@Serializer(forClass = Type::class)
class TypeSerializer {
    override val descriptor: SerialDescriptor
        get() = PrimitiveSerialDescriptor("Type", PrimitiveKind.STRING)

    override fun deserialize(decoder: Decoder): Type {
        val value = decoder.decodeString()
        return values().find { it.value == value } ?: UNKNOWN
    }

    override fun serialize(encoder: Encoder, value: Type) {
        encoder.encodeString(value.value)
    }
}

But when I tried to build the project, the console printed the following error:

org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during file facade code generation
File being compiled: file:///Users/user/AndroidStudioProjects/MyApplication/app/src/main/java/com/example/myapplication/DocMessage.kt
The root cause java.lang.IllegalArgumentException was thrown at: org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializerCodegenImpl.addSyntheticAnnotationsToDescriptor(SerializerCodegenImpl.kt:123)
    at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:239)
    at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generate(PackageCodegenImpl.java:78)
    at org.jetbrains.kotlin.codegen.DefaultCodegenFactory.generatePackage(CodegenFactory.kt:77)
    at org.jetbrains.kotlin.codegen.DefaultCodegenFactory.generateModule(CodegenFactory.kt:62)
    at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:35)
    at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:278)
    at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:171)
    at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:102)
    at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:112)
    at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:122)
    at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:86)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:252)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:243)
    at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:113)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:243)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:90)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:56)
    at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:169)
    at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:52)
    at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:92)
    at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:44)
    at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:98)
    at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:412)
    at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:112)
    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileIncrementally(IncrementalCompilerRunner.kt:358)
    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileIncrementally$default(IncrementalCompilerRunner.kt:300)
    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl$rebuild(IncrementalCompilerRunner.kt:119)
    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:170)
    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:81)
    at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:607)
    at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:96)
    at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1658)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:359)
    at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
    at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:562)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:796)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:677)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:676)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.IllegalArgumentException: Can't use arguments with defaults for serializable annotations yet
    at org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializerCodegenImpl.addSyntheticAnnotationsToDescriptor(SerializerCodegenImpl.kt:123)
    at org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializerCodegenImpl.addElementsContentToDescriptor(SerializerCodegenImpl.kt:116)
    at org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializerCodegenImpl.generateSerialDescriptor(SerializerCodegenImpl.kt:77)
    at org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializerCodegenImpl.generateSerialDesc(SerializerCodegenImpl.kt:145)
    at org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializerCodegen.generate(SerializerCodegen.kt:36)
    at org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializerCodegenImpl$Companion.generateSerializerExtensions(SerializerCodegenImpl.kt:46)
    at org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationCodegenExtension.generateClassSyntheticParts(SerializationCodegenExtension.kt:30)
    at org.jetbrains.kotlin.codegen.ImplementationBodyCodegen.generateSyntheticPartsAfterBody(ImplementationBodyCodegen.java:448)
    at org.jetbrains.kotlin.codegen.MemberCodegen.generate(MemberCodegen.java:135)
    at org.jetbrains.kotlin.codegen.MemberCodegen.genClassOrObject(MemberCodegen.java:305)
    at org.jetbrains.kotlin.codegen.MemberCodegen.genSyntheticClassOrObject(MemberCodegen.java:319)
    at org.jetbrains.kotlin.codegen.ClassBodyCodegen.generateBody(ClassBodyCodegen.java:113)
    at org.jetbrains.kotlin.codegen.MemberCodegen.generate(MemberCodegen.java:132)
    at org.jetbrains.kotlin.codegen.MemberCodegen.genClassOrObject(MemberCodegen.java:305)
    at org.jetbrains.kotlin.codegen.MemberCodegen.genClassOrObject(MemberCodegen.java:289)
    at org.jetbrains.kotlin.codegen.MemberCodegen.genClassOrObject(MemberCodegen.java:315)
    at org.jetbrains.kotlin.codegen.ClassBodyCodegen.generateDeclaration(ClassBodyCodegen.java:176)
    at org.jetbrains.kotlin.codegen.ClassBodyCodegen.generateBody(ClassBodyCodegen.java:80)
    at org.jetbrains.kotlin.codegen.MemberCodegen.generate(MemberCodegen.java:132)
    at org.jetbrains.kotlin.codegen.MemberCodegen.genClassOrObject(MemberCodegen.java:305)
    at org.jetbrains.kotlin.codegen.MemberCodegen.genClassOrObject(MemberCodegen.java:289)
    at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generateClassesAndObjectsInFile(PackageCodegenImpl.java:119)
    at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generateFile(PackageCodegenImpl.java:138)
    at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generate(PackageCodegenImpl.java:70)
    ... 47 more

This didn't happen when I disabled the databinding. To solve this, I changed the implementation of my DocMessage class

@Serializable
data class DocMessage(
    val id: Int = 0,
    @SerialName("type1")
    @JsonNames("type2")
    val type: Type
)

or

@Serializable
data class DocMessage(
    val id: Int = 0,
    val type1: Type? = null,
    val type2: Type? = null
)

But none of the solutions solves the original problem completely. @JsonNames cannot be used with more than one value and requires to be used with the @SerialName to work.

steurt commented 2 years ago

I have the same issue as @eugenio1590 describes.

I have the following enum with a JsonNames attribute with two values:

@Serializable
enum class ReportStatus(val order: Int) {
    @SerialName("E")
    ERROR(-1),

    @SerialName("O")
    OPEN(0),

    @SerialName("D")
    @JsonNames("A", "N") // Decode the A and N values as FINAL.
    FINAL(2),
}

This causes the build to fail:

org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during file facade code generation
File being compiled: file:///Users/some-user/Projects/some-project/app/src/main/java/com/somepackage/model/ReportStatus.kt
The root cause java.lang.IllegalArgumentException was thrown at: org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializerCodegenImpl.addSyntheticAnnotationsToDescriptor(SerializerCodegenImpl.kt:123)
    at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:239)
    at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generate(PackageCodegenImpl.java:78)
    at org.jetbrains.kotlin.codegen.DefaultCodegenFactory.generatePackage(CodegenFactory.kt:77)
    at org.jetbrains.kotlin.codegen.DefaultCodegenFactory.generateModule(CodegenFactory.kt:62)
    at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:35)
    at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:278)
    at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:171)
    at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:102)
    at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:112)
    at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:122)
    at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:86)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:252)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:243)
    at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:113)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:243)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:90)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:56)
    at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:169)
    at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:52)
    at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:92)
    at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:44)
    at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:98)
    at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:412)
    at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:112)
    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileIncrementally(IncrementalCompilerRunner.kt:358)
    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileIncrementally$default(IncrementalCompilerRunner.kt:300)
    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:160)
    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:81)
    at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:607)
    at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:96)
    at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1658)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:359)
    at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
    at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:562)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:796)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:677)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:676)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.IllegalArgumentException: Can't use arguments with defaults for serializable annotations yet
    at org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializerCodegenImpl.addSyntheticAnnotationsToDescriptor(SerializerCodegenImpl.kt:123)
    at org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializerForEnumsCodegen.addElementsContentToDescriptor(SerializerForEnumsCodegen.kt:64)
    at org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializerCodegenImpl.generateSerialDescriptor(SerializerCodegenImpl.kt:77)
    at org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializerCodegenImpl.generateSerialDesc(SerializerCodegenImpl.kt:145)
    at org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializerCodegen.generate(SerializerCodegen.kt:36)
    at org.jetbrains.kotlinx.serialization.compiler.backend.jvm.SerializerCodegenImpl$Companion.generateSerializerExtensions(SerializerCodegenImpl.kt:46)
    at org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationCodegenExtension.generateClassSyntheticParts(SerializationCodegenExtension.kt:30)
    at org.jetbrains.kotlin.codegen.ImplementationBodyCodegen.generateSyntheticPartsAfterBody(ImplementationBodyCodegen.java:448)
    at org.jetbrains.kotlin.codegen.MemberCodegen.generate(MemberCodegen.java:135)
    at org.jetbrains.kotlin.codegen.MemberCodegen.genClassOrObject(MemberCodegen.java:305)
    at org.jetbrains.kotlin.codegen.MemberCodegen.genSyntheticClassOrObject(MemberCodegen.java:319)
    at org.jetbrains.kotlin.codegen.ClassBodyCodegen.generateBody(ClassBodyCodegen.java:113)
    at org.jetbrains.kotlin.codegen.MemberCodegen.generate(MemberCodegen.java:132)
    at org.jetbrains.kotlin.codegen.MemberCodegen.genClassOrObject(MemberCodegen.java:305)
    at org.jetbrains.kotlin.codegen.MemberCodegen.genClassOrObject(MemberCodegen.java:289)
    at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generateClassesAndObjectsInFile(PackageCodegenImpl.java:119)
    at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generateFile(PackageCodegenImpl.java:138)
    at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generate(PackageCodegenImpl.java:70)
    ... 46 more

If i remove the second value of JsonNames, the build succeeds:

@SerialName("D")
@JsonNames("A", "N") // Decode the A and N values as FINAL.
FINAL(2),
marcosalis commented 2 years ago

Any updates on the feature above? Like @steurt , I'm trying to use @JsonNames with an enum values, but I'm getting the same exception with kapt with kotlinx-serialization-json:1.3.2. Thank you for considering supporting this! 🙏

bogdanzurac commented 2 years ago

Can confirm we've hit the same roadblock with multiple @JsonNames values as well. @sandwwraith @shanshin is there any update WRT this issue?

rockyoung commented 2 years ago

We also encountered the same problem while using kapt (for dagger) together with serialization in a same module:

@Serializable
data class A(val p1: Int, val p2: Float)

@Serializable
data class B(
    @JsonNames("id1", "id2") // this is ok, compile passed.
    val id: String,
    @JsonNames("result1", "result2") // compile error!
    val result: List<A>,
)
MisterPotz commented 1 year ago

Seems there is kind of a workaround for this problem (using generic serializer, and not creating a typed version of it for each type parameter). I declared parameterized typealias, with the value class being annotated with Serializable that refers to the generic serializer class, which accepts the generic parameter.

typealias SafeList<T> = @Serializable(with = SafeListSerializer::class) KindListWrapper<T>

where the implementations are as follows:

KindListWrapper implementation

```kotlin class KindListWrapper(private val list: List) : List by list { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as KindListWrapper<*> if (list != other.list) return false return true } override fun hashCode(): Int { return list.hashCode() } override fun toString(): String { return list.toString() } } ```

SafeListSerializer implementation

```kotlin @OptIn(ExperimentalSerializationApi::class) @Serializer(forClass = KindListWrapper::class) class SafeListSerializer( private val valueSerializer: KSerializer, ) : KSerializer> { private var throwableHandler: ((Throwable) -> Unit)? = null constructor(elementSerializer: KSerializer, throwableHandler: ((Throwable) -> Unit)?) : this( elementSerializer ) { this.throwableHandler = throwableHandler } private val listSerializer = ListSerializer(valueSerializer) override val descriptor: SerialDescriptor = listSerializer.descriptor override fun deserialize(decoder: Decoder): KindListWrapper { val values = mutableListOf() val compositeDecoder = decoder.beginStructure(descriptor) while (true) { val index = compositeDecoder.decodeElementIndex(descriptor) if (index == CompositeDecoder.DECODE_DONE) break values.add(readElement(compositeDecoder)) } compositeDecoder.endStructure(descriptor) return KindListWrapper(values.filterNotNull()) } override fun serialize(encoder: Encoder, value: KindListWrapper) { listSerializer.serialize(encoder, value) } private fun readElement(compositeDecoder: CompositeDecoder): T? { return try { val jsonDecoder = compositeDecoder as JsonDecoder val jsonElement = jsonDecoder.decodeJsonElement() jsonDecoder.json.decodeFromJsonElement(valueSerializer, jsonElement) } catch (e: Exception) { throwableHandler?.invoke(e) null } } } ```

Solution is based on answers from discussion #1205 , specifically on this and this This worked for kotlin version 1.8.10, don't know how this will work for the earlier versions.

yerlansarsenov commented 9 months ago

MisterPotz hello! I have used your solution so far, and find out the following problem. When we use SafeList in nested way, like here:

@Serializable
data class Contact(
    val name: String,
    val phone: String
)

@Serializable
data class User(
    val contacts: SafeList<Contact>
)

@Serializable
data class Response(
    val result: SafeList<User>
)

So, if we try to decode json like this:

{
    "result": [
        {
            "contacts": [
                {
                    "name": "David",
                    "phone": "123"
                },
                {
                    "name": "John",
                    "phone": "456"
                }
            ]
        },
        {
            "contacts": [
                {
                    "name": "George",
                    "phone": "321"
                },
                {
                    "name": "Lucas",
                    "phone": "654"
                }
            ]
        }
    ]
}

we will face strange behaviour:

kotlinx.serialization.json.internal.JsonDecodingException: Expected class kotlinx.serialization.json.JsonObject (Kotlin reflection is not available) as the serialized body of model.data.serializer.Contact, but had class kotlinx.serialization.json.JsonArray (Kotlin reflection is not available)
    at kotlinx.serialization.json.internal.JsonExceptionsKt.JsonDecodingException(JsonExceptions.kt:24)
    at kotlinx.serialization.json.internal.AbstractJsonTreeDecoder.beginStructure(TreeJsonDecoder.kt:358)
yerlansarsenov commented 9 months ago

this solution (about custom serializer SafeListSerializerStack) is pretty forward, it worked for me