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

Parse children as Element or Text / CDATA #253

Closed
AdrienDaily opened this issue Nov 19, 2024 · 8 comments
Closed

Parse children as Element or Text / CDATA #253

AdrienDaily opened this issue Nov 19, 2024 · 8 comments

Comments

@AdrienDaily
Copy link

AdrienDaily commented Nov 19, 2024

Hello,

Using the version 0.86.3

I'm having trouble getting from a children either an Element or a Text / CDATA result.

<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>
</Extensions>
@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) {
                subclass(String::class)
                subclass(AdVerificationsDto::class)
            }
        }
    }
}

@Serializable
@SerialName("AdVerifications")
data class AdVerificationsDto(
    @XmlElement(true)
    var verifications: MutableList<VerificationDto>? = mutableListOf()
)

I'm not sure if it's the right way to handle it and I get this error:

java.lang.IllegalArgumentException: Polymorphic value has not been read for class null
                                    	at kotlinx.serialization.internal.AbstractPolymorphicSerializer.deserialize(AbstractPolymorphicSerializer.kt:67)
                                    	at nl.adaptivity.xmlutil.serialization.XmlDecoderBase$AnonymousListDecoder.decodeSerializableElement(XMLDecoder.kt:905)
                                    	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$TagDecoder.decodeSerializableElement(XMLDecoder.kt:459)

Thanks a lot for your help

@pdvrieze
Copy link
Owner

You probably need to make sure to use autopolymorphic mode. I've created a test for this. The following code works for me (note that I chose to parse content of Adverifications as Element rather than creating a class for verification).

class StringOrCompositePolymorphism253 {

    val xml = XML(ExtensionDto.module()) { recommended_0_90_2() }

    @Test
    fun testParse() {
        val result = xml.decodeFromString<List<ExtensionDto>>(SAMPLE, QName("Extensions"))
        assertEquals(2, result.size)
        assertEquals("{\"savedData\":\"\"}", assertIs<String>(result[0].value[0]).trim())
        for (e in result[1].value) {
            if(e is String) assertEquals("", e.trim())
        }
        val e2 = result[1].value.filterIsInstance<AdVerificationsDto>().single()
        assertEquals(1, e2.verifications?.size)
        assertIs<Element>(e2.verifications?.single())
    }


    @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) {
                    subclass(String::class)
                    subclass(AdVerificationsDto::class)
                }
            }
        }
    }

    @Serializable
    @SerialName("AdVerifications")
    data class AdVerificationsDto(
        @XmlElement(true)
        @XmlValue
        var verifications: MutableList<Element>? = mutableListOf()
    )

    val SAMPLE = """
        |<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>
        |</Extensions>
    """.trimMargin()
}

pdvrieze added a commit that referenced this issue Nov 19, 2024
configuration that does not have autopolymorphism enabled.
@AdrienDaily
Copy link
Author

Hello,

It solved my issue by using the autoPolymorphic parameter that I missed.

My final configuration looks like this after updating to: 0.90.3

    @OptIn(ExperimentalXmlUtilApi::class)
    val parser = XML(ExtensionDto.module()) {
        policy = DefaultXmlSerializationPolicy(formatCache = DefaultFormatCache()) {
            autoPolymorphic = true
            isStrictAttributeNames = false
        }
    }

Thanks a lot for your time and help.

@pdvrieze
Copy link
Owner

pdvrieze commented Nov 21, 2024

Autopolymorphic is needed here, otherwise it expects a wrapper for polymorphism. The reason it is not the default is for compatibility.
Btw. note that the DefaultFormatCache is not thread safe, but mainly helpful if you use the format for multiple parse/encode cycles (parsing and encoding uses the same extended descriptors so share their caching)

@AdrienDaily
Copy link
Author

AdrienDaily commented Nov 21, 2024

Thanks for the precisions, and for pointing out the DefaultFormatCache, I just checked the comments on the FormatCache abstraction.

It seems better to use the defaultSharedFormatCache method then to handle multithreading safety properly ?

Do you know if the performance cost is still better than having no cache at all in most cases ?

@pdvrieze
Copy link
Owner

The multithreading version uses threadlocal "copies" of the cache. The "local" cache is acquired once at the start of (de)serialization as that by necessity is within the same thread. As such the overhead should be fairly limited as compared to not having the safety. What the overhead is of the cached (metadata) process depends a bit on the structure of your files/documents. For XML Schema the initial creation of the metadata is significant, but xml schema is hardly a simple document format.

@AdrienDaily
Copy link
Author

Thanks for the details it works fine with the defaultSharedFormatCache on our side.

But I found a new issue when there is an optional Element like:

<Extension type="geo">
      <Country>IE</Country>
</Extension>

I tried to add a defaultDeserializer to the polymorphic

 @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 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):

 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)

@AdrienDaily AdrienDaily reopened this Nov 27, 2024
@pdvrieze
Copy link
Owner

@AdrienDaily That is a separate issue. Given your error it seems that for xml I need to add explicit support for a default serializer (I will explore how/why this can be done). At the same time, you can set the policy to have an unknownChildHandler that just ignores the tag.

@AdrienDaily
Copy link
Author

AdrienDaily commented Nov 27, 2024

Ok thanks I will make a separate issue: #256

I have an unknownChildHandler but it doesn't change anything, I might be using it wrong.

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

The test class for #253 is updated accordingly with various alternative
approaches.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants