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

[v0.90.3] List<@Polymorphic Any> doesn't support defaultDeserializer #256

Open
AdrienDaily opened this issue Nov 27, 2024 · 5 comments
Open
Labels
indev The issue is fixed/implemented in the dev branch

Comments

@AdrienDaily
Copy link

AdrienDaily commented Nov 27, 2024

Hello,

I'm having trouble to have a fallback on a List<@polymorphic Any> with optional children (non handled atm).

Given this example XML:

<Extensions>
    <Extension source="mySource">
        <![CDATA[{"savedData":""}]]>
    </Extension>
    <Extension type="AdVerifications">
        <AdVerifications>
            <Verification vendor="Something">
                <JavaScriptResource apiFramework="omid" browserOptional="true">
                    <![CDATA[https://google.com/video.js]]>
                </JavaScriptResource>
                <VerificationParameters>
                    <![CDATA[{"key":"21649"}]]>
                </VerificationParameters>
            </Verification>
        </AdVerifications>
    </Extension>
    <Extension type="geo">
        <Country>IE</Country>
        <Bandwidth>4</Bandwidth>
    </Extension>
</Extensions>

I'm trying to solve it like this:

 @Serializable
@SerialName("Extension")
data class ExtensionDto(
    val source: String? = null,
    val type: String? = null,
    @XmlValue
    var value: List<@Polymorphic Any> = listOf()
) {
    companion object {
        fun module() = SerializersModule {
            polymorphic(Any::class) {
                defaultDeserializer { Unknown.serializer() }
                subclass(String::class)
                subclass(AdVerificationsDto::class)
            }
        }
    }
}

@Serializable
class Unknown

I also tried using:

fun module() = SerializersModule {
    polymorphicDefaultDeserializer(Any::class) { UnknownType.serializer() }
    polymorphic(Any::class) {
        subclass(AdVerificationsDto::class)
        subclass(String::class)
    }
}

And the parser setup:

object XmlParser {
     @OptIn(ExperimentalXmlUtilApi::class)
    val parser = XML(ExtensionDto.module()) {
        policy = DefaultXmlSerializationPolicy(formatCache = defaultSharedFormatCache()) {
            autoPolymorphic = true
            unknownChildHandler = UnknownChildHandler { _, _, _, _, _ -> emptyList() }
        }
    }
}

I also tried using a wrapper but couldn't solve the issue with the string type as I can't use it as a subclass of a specific wrapper type.

The error I get when trying to parse this country field (that we don't handle ATM so just returning null or an empty class like the Unknown one would be fine)

The unknownChildHandler seems to not be used as a fallback either.
I tried removing the default serializer without success.

 error (exception=nl.adaptivity.xmlutil.serialization.XmlParsingException: Invalid XML value at position: 137:23: No XmlSerializable found to handle unrecognized value tag Country in polymorphic context)
                                    nl.adaptivity.xmlutil.serialization.XmlParsingException: Invalid XML value at position: 137:23: No XmlSerializable found to handle unrecognized value tag Country in polymorphic context
                                    	at nl.adaptivity.xmlutil.serialization.XmlDecoderBase.deserializeSafe(XMLDecoder.kt:2229)
                                    	at nl.adaptivity.xmlutil.serialization.XmlDecoderBase$AnonymousListDecoder.decodeSerializableElement(XMLDecoder.kt:1701)
                                    	at kotlinx.serialization.encoding.CompositeDecoder$DefaultImpls.decodeSerializableElement$default(Decoding.kt:538)
                                    	at kotlinx.serialization.internal.CollectionLikeSerializer.readElement(CollectionSerializers.kt:80)
                                    	at kotlinx.serialization.internal.AbstractCollectionSerializer.readElement$default(CollectionSerializers.kt:51)
                                    	at kotlinx.serialization.internal.AbstractCollectionSerializer.merge(CollectionSerializers.kt:36)
                                    	at nl.adaptivity.xmlutil.serialization.XmlDecoderBase$TagDecoderBase.decodeSerializableElement(XMLDecoder.kt:900)
                                    	at com.dailymotion.adsharedsdk.feature.vastparser.data.dto.ExtensionDto$$serializer.deserialize(VastDto.kt:168)
                                    	at com.dailymotion.adsharedsdk.feature.vastparser.data.dto.ExtensionDto$$serializer.deserialize(VastDto.kt:168)
                                    	at nl.adaptivity.xmlutil.serialization.XmlDecoderBase.deserializeSafe(XMLDecoder.kt:111)
                                    	at nl.adaptivity.xmlutil.serialization.XmlDecoderBase$AnonymousListDecoder.decodeSerializableElement(XMLDecoder.kt:1701)
                                    	at kotlinx.serialization.encoding.CompositeDecoder$DefaultImpls.decodeSerializableElement$default(Decoding.kt:538)
                                    	at kotlinx.serialization.internal.CollectionLikeSerializer.readElement(CollectionSerializers.kt:80)
                                    	at kotlinx.serialization.internal.AbstractCollectionSerializer.readElement$default(CollectionSerializers.kt:51)
                                    	at kotlinx.serialization.internal.AbstractCollectionSerializer.merge(CollectionSerializers.kt:36)
                                    	at nl.adaptivity.xmlutil.serialization.XmlDecoderBase$TagDecoderBase.decodeNullableSerializableElement(XMLDecoder.kt:976)

Thanks again for your help !

@pdvrieze
Copy link
Owner

Unfortunately the error message isn't particularly clear. What happens in this case is that the system tries to find a mapped serializer (note that there is a complex tag ←→ serialname mapping going on here). If it doesn't find one, it tries to find a single serializer that implements the XmlDeserializer interface, and if it finds one it delegates parsing that tag to that "xml special casing serializer - e.g. the node/element serializers). You can have your own one, but make sure it consumes the tag.

I've been checking, this is not a simple case to handle as the default xml approach doesn't actually record the full type name of the element (but the polymorphic parser still wants that). As a workaround you can just add Element or Node as one of the subclasses (actually any serializer that implements XmlDeserializer) as fallback which will then need to consume the tag. Unfortunately recovering from unknown content doesn't yet work in this case (an @XmlValue list). I'll work on triggering the unknown child handling, hopefully in a way that does not involve a lot of redundant special case checks (we are looking at the contents of the tag being an anonymous list with transparently polymorphic children)

@AdrienDaily
Copy link
Author

AdrienDaily commented Nov 28, 2024

I better understand the issue now, very insightful thanks !

I just tried the workaround registering an Element as subclass and it's working.

We filter unhandled values anyway, it was mainly to avoid any throw which could break the <Extensions> root node parsing.
Having a way to "ignore" unregistered classes would be really nice but at least we can work with it.

Thanks again for your help.

pdvrieze added a commit that referenced this issue Nov 28, 2024
There is even an additional feature in that recovery will attempt to
detect recovery that doesn't consume a tag and consume it instead.
@pdvrieze
Copy link
Owner

@AdrienDaily I've managed to get the first half working (triggering the unknown child handler). Have a look at my test for your case: https://github.com/pdvrieze/xmlutil/blob/dev/serialization/src/commonTest/kotlin/nl/adaptivity/xml/serialization/regressions/StringOrCompositePolymorphism253.kt

Element should also work (make sure to use the correct Element type: nl.adaptivity.xmlutil.dom2.Element) that uses the correct element parser.

pdvrieze added a commit that referenced this issue Nov 28, 2024
…alizer.

The test class for #253 is updated accordingly with various alternative
approaches.
@pdvrieze
Copy link
Owner

I've just updated the code and it should work in the current dev branch (In principle it should end up as a snapshot release soon - but oss has been a bit patchy lately).

@pdvrieze pdvrieze added the indev The issue is fixed/implemented in the dev branch label Nov 28, 2024
@pdvrieze
Copy link
Owner

There is some further work in dev on this (including ignoring whitespace)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
indev The issue is fixed/implemented in the dev branch
Projects
None yet
Development

No branches or pull requests

2 participants