kotlinx.serialization icon indicating copy to clipboard operation
kotlinx.serialization copied to clipboard

Generic custom serializer breaks kotlin-kapt compilation

Open alexvanyo opened this issue 5 years ago • 11 comments

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

  • Kotlin version: 1.3.61
  • Library version: 0.14.0
  • Kotlin platform: JVM
  • Gradle version: 5.4.1
  • IDE version: Android Studio 3.5.3

alexvanyo avatar Feb 02 '20 20:02 alexvanyo

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!

alexvanyo avatar Mar 29 '20 01:03 alexvanyo

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

teal77 avatar Jul 28 '20 14:07 teal77

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.

alexvanyo avatar Aug 07 '20 17:08 alexvanyo

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.

eugenio1590 avatar Sep 20 '21 22:09 eugenio1590

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),

steurt avatar Nov 19 '21 08:11 steurt

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! 🙏

marcosalis avatar May 02 '22 15:05 marcosalis

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

bogdanzurac avatar May 16 '22 10:05 bogdanzurac

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>,
)

rockyoung avatar Aug 17 '22 06:08 rockyoung

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


class KindListWrapper<T : Any>(private val list: List<T>) : List<T> 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


@OptIn(ExperimentalSerializationApi::class)
@Serializer(forClass = KindListWrapper::class)
class SafeListSerializer<T : Any>(
    private val valueSerializer: KSerializer<T>,
) : KSerializer<KindListWrapper<T>> {
    private var throwableHandler: ((Throwable) -> Unit)? = null

    constructor(elementSerializer: KSerializer<T>, 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<T> {
        val values = mutableListOf<T?>()
        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<T>) {
        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.

MisterPotz avatar Mar 10 '23 13:03 MisterPotz

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 avatar Jan 29 '24 10:01 yerlansarsenov

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

yerlansarsenov avatar Jan 29 '24 11:01 yerlansarsenov