Skip to content

Custom serializers can't be found on Kotlin/JS or Kotlin/Native if the entity implementing KSerializer is a class rather than an object #2382

Open
@st4r1ight

Description

@st4r1ight

Describe the bug
When writing a custom serializer in a project targeting Kotlin/JS or Kotlin/Native, the compiler plugin won't find the serializer if the entity that implements KSerializer<T> is a class, and will throw an error. If the entity is an object, however, the custom serializer will be found and the class will be serialized properly. When run on a NodeJS target, the serializer being a class will cause Node to exit with exit code 4, 5 or 6, although I don't know if this is something Node does in general for serialization errors. Kotlin/JVM will find the custom serializer correctly whether or not the KSerializer<T> is an object, and the JVM target also works correctly when it's part of a multiplatform project.

I will note that all my tests have been run using a unit testing framework (Kotest) because I don't know very much about multiplatform code and I haven't yet figured out how to run any code independently. I'd be happy to run some tests outside of Kotest if someone could point me in the right direction for running code on JS or Native.

To Reproduce
In a multiplatform project, write a custom serializer for a class.

class Dog { ... }

class DogSerializer : KSerializer<Dog> { ... }

Mark the class as serializable with the custom serializer:

@Serializable(with = DogSerializer::class)
class Dog { ... }

Attempt to serialize using Kotlin/JS. Gradle throws the following error:

Serializer for class 'JsonTextColour' is not found.
Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.

On Kotlin/JS explicitly declared serializer should be used for interfaces and enums without @Serializable annotation

Or, if you are targeting NodeJS, you get this rather cryptic error:

/home/user/.gradle/nodejs/node-v18.12.1-linux-x64/bin/node exited with errors: 6

However, if you change the definition of class DogSerializer to an object...

@Serializable(with = DogSerializer::class)
class Dog { ... }

object DogSerializer : KSerializer<Dog> { ... }

the compiler is able to find the serializer.

Here's the code where I first experienced the bug:

@JvmInline
@Serializable(with = JsonTextColour.Serializer::class)
value class JsonTextColour(val hex: Int) {
    companion object {
        fun fromString(colourString: String)  { ... }
    }

    /**
     * [KSerializer] for [JsonTextColour].
     * @see toString
     * @see fromString
     * */
    internal class Serializer : KSerializer<JsonTextColour> {
        override val descriptor = PrimitiveSerialDescriptor(
            serialName = "JsonText.Serializer",
            kind = PrimitiveKind.STRING
        )

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

        override fun deserialize(decoder: Decoder): JsonTextColour {
            val jsonString = decoder.decodeString()
            return fromString(jsonString)
        }
    }

(I know that the serializer is not using the recommended strategy for value classes; that's a planned improvement but I did test it using encodeInline() as well).

I also tested it with the custom serializer in its own file and in an outer class.

Expected behavior
The serialization plugin should correctly detect and use the custom serializer, as it does in Kotlin/JVM

Environment

  • Kotlin version: [e.g. 1.3.30] 1.8.21 (also tested on 1.9.0)
  • Library version: [e.g. 0.11.0] 1.5.1
  • Kotlin platforms: [e.g. JVM, JS, Native or their combinations] Kotlin/JS and Kotlin/Native - my project includes JVM support, but as I mentioned, Kotlin/JVM is not affected by this bug.
  • Gradle version: [e.g. 4.10] 7.6.0 - I also tried on 8.2.1
  • IDE version (if bug is related to the IDE) [e.g. IntellijIDEA 2019.1, Android Studio 3.4]
  • Other relevant context [e.g. OS version, JRE version, ... ] Fedora 38. Kotlin was targeting Java 17 and NodeJS 18.12.1. Tests were run using Kotest 5.6.2., and I used Firefox 115.0.2 for testing the browser JS.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions