a

    Anarakul

    1 year ago
    I am trying to use serialization with an
    enum class
    . I have read the appropriate documents (I think) and am using the appropriate annotations and strategies (I think), but I am still encountering a run-time issue immediately. I am also encountering a compile-time issue, I think, because the compiler doesn’t believe that there’s a
    serializer
    method on my
    enum class
    .
    I am using
    1.4.21
    for everything I can, and
    1.0-M1-1.4.0-rc
    for the run-time.
    This is my best guess at what versions I should be using, so I’d appreciate any correction if someone has it.
    So I have an enum:
    @Serializable(with = TransformationRuleSerializer::class)
    enum class TransformationRule(val identifier: String)
    And a trivial serializer:
    @Serializer(forClass = TransformationRule::class)
    object TransformationRuleSerializer
    {
    	override val descriptor
    		get() = PrimitiveSerialDescriptor(
    			"TransformationRuleSerializer",
    			PrimitiveKind.STRING)
    
    	override fun deserialize(decoder: Decoder): TransformationRule
    	{
    		val serializedName = decoder.decodeString()
    		return TransformationRule.values().find {
    			it.identifier == serializedName
    		}!!
    	}
    
    	override fun serialize(encoder: Encoder, value: TransformationRule) =
    		encoder.encodeString(value.identifier)
    }
    Eliding most of the stack for the moment and going to the root cause, at run-time I receive this error:
    Caused by: kotlinx.serialization.SerializationException: Serializer for class 'TransformationRule' is not found. Mark the class as @Serializable or provide the serializer explicitly.
    I have tried a number of variations on this scheme in order to extract more information or find a path to solution.
    I am using JVM only, I should mention.
    And I am using JSON as the serialization transport.
    Also, this used to work. But somehow a project that I had not touched for a year atrophied in place, and I was no longer able to build it. After struggling with Gradle and/or IntelliJ, together or separately, for a whole day, I gave up and migrated everything forward to the latest versions. I have no remaining deprecation warnings — actually, I have no warnings of any kind — but the system fails at run-time with the error message that I reported above.
    My
    enum
    is serialized as a property of an enclosing
    data class
    , so I thought that this open issue might be relevant: https://github.com/Kotlin/kotlinx.serialization/issues/487
    But I tried the workarounds described therein, and it did nothing for me; same situation after as before.
    Here is my enclosing data class:
    /**
     * A transformation encapsulates a rule and its static operands, and provides a
     * simple API for applying the transformation to a [JSON element][JsonElement].
     */
    @Serializable(with = TransformationSerializer::class)
    data class Transformation(
    	val rule: TransformationRule,
    	val operands: List<String>
    )
    {
    	/**
    	 * Transform the specified [JSON element][JsonElement] by applying the
    	 * encapsulated [transformation rule][TransformationRule]. If the supplied
    	 * JSON element does not satisfy the preconditions of the rule, then it will
    	 * not be transformed.
    	 *
    	 * @param source
    	 *   The JSON element, which is expected to satisfy the prerequisites of the
    	 *   rule.
    	 * @return
    	 *   The result of the transformation. `null` indicates that the object is
    	 *   being elided.
    	 */
    	fun transform(source: JsonElement): JsonElement? =
    		rule.transform(operands, source)
    }
    It uses a custom serializer, primarily so that it can cope with two different representational strategies based on possible compactness:
    @Serializer(forClass = Transformation::class)
    object TransformationSerializer
    {
    	override val descriptor =
    		buildClassSerialDescriptor("TransformationSerializer") {
    			element<TransformationRule>("rule")
    			element("operands", listSerialDescriptor<String>())
    		}
    
    	override fun deserialize(decoder: Decoder): Transformation
    	{
    		return try
    		{
    			val ruleName = decoder.decodeString()
    			Transformation(
    				TransformationRule.values().find {
    					it.identifier == ruleName
    				}!!,
    				emptyList())
    		}
    		catch (e: SerializationException)
    		{
    			decoder.decodeStructure(descriptor) {
    				var rule: TransformationRule? = null
    				var operands: List<String>? = null
    				while (true)
    				{
    					when (val index = decodeElementIndex(descriptor))
    					{
    						0 -> rule = decodeSerializableElement(
    							descriptor,
    							index,
    							TransformationRuleSerializer)
    						1 -> operands = decodeSerializableElement(
    							descriptor,
    							index,
    							ListSerializer(String.serializer()))
    						DECODE_DONE -> break
    					}
    				}
    				Transformation(rule!!, operands!!)
    			}
    		}
    	}
    
    	override fun serialize(encoder: Encoder, value: Transformation)
    	{
    		if (value.rule.arity == 0 || value.operands.isEmpty())
    		{
    			encoder.encodeString(value.rule.identifier)
    		}
    		else
    		{
    			encoder.encodeStructure(descriptor) {
    				encodeSerializableElement(
    					descriptor,
    					0,
    					TransformationRuleSerializer,
    					value.rule)
    				encodeSerializableElement(
    					descriptor,
    					1,
    					ListSerializer(String.serializer()),
    					value.operands)
    			}
    		}
    	}
    }
    Essentially, I’m just choosing between a plain string representation (for an arity-0 transformation) and a simple object representation (for an arity-n transformation, where n > 0).
    So e.g. a serialized transformation might be either:
    "hash"
    Or:
    {
    	name: "salt",
    	arguments: ["BDF0E4EA-5C5F-4FF1-822D-64CDF6FC0D15"]
    }
    I have no idea whether the code that I’ve written is correct, because I can’t even get past the basics. The plugin doesn’t appear to be generating the correct code for associating my
    enum class TransformationRule
    with its serializer (
    TransformationRuleSerializer
    ).
    Also worth mentioning perhaps, I’m neither a newcomer to Kotlin or to kotlinx-serialization. I have written several very large projects in Kotlin and I maintain a large project actively. I use Kotlin both at work and for play.
    Okay, that’s all that I can think to volunteer without some engagement. I’ll check on this frequently, and would greatly appreciate any help that can be offered. Thanks in advance.
    rnett

    rnett

    1 year ago
    Your runtime version doesn't match your plugin version, try the latest runtime (
    1.0.1
    ). There probably should be better errors if that mismatches, but the runtime is for Kotlin and plugin version
    1.4.0-RC
    . If that doesn't help, try without custom serializers and see if it works. Everything looks correct to me though. I think Enums serialize to their names by default in the new version, too, so you probably don't need a custom serializer there.
    a

    Anarakul

    1 year ago
    Ah! It took me a bit to figure out how to set
    1.0.1
    in Gradle (the URL was eluding me), but that seems to have been the problem. Now it’s warning me that I’m using tons of experimental APIs, and it still crashes on startup, but with an error in my own code. Fingers crossed, but I should be able to trace it from here.
    Thank you for your help, @rnett. Much appreciated!
    FYI, I got everything working with my custom serializers. Thanks agin, @rnett. Really appreciate the help. 👍