Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic custom serializer breaks kotlin-kapt compilation #685

Open
alexvanyo opened this issue Feb 2, 2020 · 11 comments
Open

Generic custom serializer breaks kotlin-kapt compilation #685

alexvanyo opened this issue Feb 2, 2020 · 11 comments

Comments

@alexvanyo
Copy link

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
Copy link
Author

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
Copy link

teal77 commented Jul 28, 2020

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

@alexvanyo
Copy link
Author

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,
JetBrains/kotlin@66754e6,
and JetBrains/kotlin@a0778ad, 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
Copy link

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
Copy link

steurt commented Nov 19, 2021

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
Copy link

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
Copy link

bogdanzurac commented May 16, 2022

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

@rockyoung
Copy link

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
Copy link

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.

@yerlansarsenov
Copy link

yerlansarsenov commented Jan 29, 2024

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
Copy link

yerlansarsenov commented Jan 29, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests