0

I was trying to use one of the Kotlin's serialization package with the following custom example class. I will extend this class matching the real use case by adding a List of the real class (which also need to be serialized), but right now the problem simply lies in de-serialization. I've managed to make the serialization works (in the second snippet of code there's both the object to be serialized and the resulting String) and seems that the de-serialization process requires to be done in the same way (i.e., by using an instance of NullableSerializer) (in fact, I haven't found a simple and correct way to de-serialize an encoded object right now. Here's the class, simple but wannabe-complex.

@Serializable(with = EhEH.Companion::class)
data class EhEH(
    val i: Int
    , val txt: String
    //, val list: Array<String> // next feature, also with complex data, not only "simple" Strings
) {

    @Serializer(forClass = EhEH::class)
    companion object : KSerializer<EhEH> {
        public var deserialEhEH: DeserializationStrategy<EhEH> = EhEH


        override val descriptor: SerialDescriptor =
            SerialDescriptor("EhEHSerializer", PrimitiveKind.STRING)

        override fun serialize(encoder: Encoder, obj: EhEH) =
            encoder.encodeString(obj::class.qualifiedName!!)

        override fun deserialize(decoder: Decoder): EhEH {
            val dec = decoder.beginStructure(descriptor)
            var txt: String? = null
            var x: Int? = null
            var i = -1
            var notFinished = true
            do {
                i = dec.decodeElementIndex(descriptor)
                when (i) {
                    CompositeDecoder.READ_DONE -> notFinished = false
                    0 -> x = dec.decodeStringElement(descriptor, i).toIntOrNull()
                    1 -> txt = dec.decodeStringElement(descriptor, i)
                    else -> throw SerializationException("Unknown index $i")
                }
            } while (notFinished)
            dec.endStructure(descriptor)
            return EhEH(
                x ?: throw MissingFieldException("x"),
                txt ?: throw MissingFieldException("txt")
            )
        }

    }

    override fun toString(): String {
        return "EhEH(i=$i, s='$txt')"
    }
}

The manual-test function (whose value is simply printed) is:

@InternalSerializationApi
    fun testCborString(): String {
        var e: EhEH = EhEH(
            7,
            "poreccio"
            //, listOf("just", "another", "vacuum", "test")
        )
        return Cbor.dumps(
            NullableSerializer(
                EhEH.Companion
            ), e
        ) + " <-> " + (
                {
                    (Cbor.load(
                        NullableSerializer(
                            EhEH.Companion
                        ),
                        Cbor.dump(
                            NullableSerializer(EhEH.Companion), e
                        )
                        //the dumped value should be "781d62632e7472797669756d2e6170692e726573706f6e7365732e45684548"
                    )
                            as EhEH).toString()
                } as () -> String)()
    }

When I try to run it, the exception lies in the Cbor.load call and is:

kotlinx.serialization.cbor.CborDecodingException: Expected start of map, but found 78

Am I the only one in WHOLE internet having this problem?

Marco Ottina
  • 399
  • 5
  • 12

1 Answers1

1

I found a way by myself, the errors was two basically:

  1. the override val descriptor: SerialDescriptor variable was setted to recognize String but that's obviously not the desired one because it's a Class:

    // val stringArraySerializer= ArraySerializer(String.serializer())
    SerialDescriptor("EhEHSerializer", StructureKind.CLASS) {
            element<Int>("i")
            element<String>("txt")
            // element<Array<String>>("list") // kept here for future references for Array of primitive stuffs
            // element("arrComplexClass", AComplexClassTaggedSerializable.descriptor) // in case of Array of complex objects, a tag @Serializable and a companion object implementing Kserializable<AComplexClassTaggedSerializable> is needed
        }
    
  2. similarly, the method override fun serialize(encoder: Encoder, obj: EhEH) was implemented to produce String (in particular a string holding the class's name). The required implementation is:

    override fun serialize(encoder: Encoder, obj: EhEH){
        var ce = encoder.beginStructure(
            descriptor,
            Int.serializer(),
            String.serializer(),
            //stringArraySerializer, // kept here for future references for
            // serializerArray_AComplexClassTaggedSerializable
        )
        ce.encodeIntElement(descriptor, 0, obj.i)
        ce.encodeStringElement(descriptor, 1, obj.txt)
        /*ce.encodeSerializableElement(
            descriptor, 2,
            stringArraySerializer, obj.arrOfStrings
        )*/
        /*ce.encodeSerializableElement(
            descriptor, 3,
            serializerArray_AComplexClassTaggedSerializable,
            obj.arrComplexClass
        )*/
        ce.endStructure(descriptor)
    
    }
    

    Also, other fundamental notes:

  3. in needed to wrap complex objects over NullableSerializer, also in case of Array of them:

    val serializerArray_AComplexClassTaggedSerializable =
        ArraySerializer<AComplexClassTaggedSerializable, AComplexClassTaggedSerializable?>(
            NullableSerializer(
                AComplexClassTaggedSerializable.Companion
            )
        ) // it's a variable used on the code above
    
  4. upon serializing and deserializing (i.e.: Cbor.dump and Cbor.load respectively) a NullableSerializer is required as first parameter:

    var nse = NullableSerializer(AComplexClassTaggedSerializable)
    println( Cbor.dump(nse, someInstanceOfThatComplexClass ).toString())
    //and....
    var o = (Cbor.load(nse, "...here go something..") as EhEH
    // use "o"
    
Marco Ottina
  • 399
  • 5
  • 12