xmlutil icon indicating copy to clipboard operation
xmlutil copied to clipboard

Deserialize a child whether it's an Element or an Attribute

Open hyphenrf opened this issue 1 year ago • 5 comments

Is it currently possible to ignore child type and deserialize it just for the fact it's a child? Here's a minimal setup:

val xml = XML { }

@Serializable
data class Data(val child : String)

// Should be able to do both
xml.decodeFromString(Data.serializer(), "<Data child='child1'/>") // OK
xml.decodeFromString(Data.serializer(), "<Data><child>child1</child></Data>") // NOPE

For reference, I'm trying to interface with a server I don't control and it sometimes returns the first format, sometimes the second, depending on the running version (with no indication of the running version in the response!)

hyphenrf avatar Feb 08 '25 16:02 hyphenrf

You should be able to use either @XmlValue otherChildren: List<Node> or @XmlValue otherChildren: List<CompactFragment>

pdvrieze avatar Feb 09 '25 12:02 pdvrieze

But the format doesn't support this fallback automatically. You could try a custom recovery in the policy.

pdvrieze avatar Mar 04 '25 11:03 pdvrieze

Before asking this question, I checked if one of the policy attributes could've helped me achieve my goal, and I couldn't figure it out. I suppose what you're suggesting is that I implement a XmlSerializationPolicy instead? Or even override the default one? Or do you provide a shortcut that I'm not aware of, like unknownChildHandler?

Thanks for your guidance

hyphenrf avatar Mar 08 '25 08:03 hyphenrf

I am suggesting unknown child handler (this doesn't require updating the policy), as you can return the resolved property. I haven't tested it though in terms of whether it is able to ignore the input kind (tag vs attribute). An alternative would be to transform the input.

pdvrieze avatar Mar 09 '25 19:03 pdvrieze

Transforming the input to "normalize" it, converting child elements to attrs (and in the process dropping their own attrs) was what I went for.

I think I had trouble doing it via the unknownChildHandler because I didn't know if what I was doing was correct. Here's what I tried for posterity:

UnknownChildHandler { input, kind, _, qname, candidates ->
    if (kind == InputKind.Element)
        candidates
            .filterIsInstance<PolyInfo>()
            .filter { it.tagName == qname }
            .map { XML.ParsedData(it.index, input.allText() }
    else
        emptyList()
}

I'm not too familiar with the API (first time using it) and I couldn't find docs/examples for how the handler is used. Maybe there's something wrong with doing it this way. I did some guessing, some source reading, some testing, and it seemed to work. I was hesitant to go for it nonetheless. This snippet is public domain. feel free to include in the examples if you deem it correct and worth including.

hyphenrf avatar Mar 29 '25 18:03 hyphenrf

@hyphenrf (and readers) the most important consideration for recovery is to leave the input in a valid state. In this case I would go for .single/.singleOrNull instead of .filter for a bit more robustness (although you should only have a single element in the list anyway - the alternative would break decoding in many ways).

pdvrieze avatar Oct 21 '25 14:10 pdvrieze