Using JAXB to marshall an XML node that can contai

2019-07-22 01:25发布

I'm trying to create JAXB annotated classes to generate XML based on the Microsoft SharePoint Query schema. I have an SpWhereClause class:

@XmlType(name="Where")
@XmlAccessorType(XmlAccessType.FIELD)
public class SpWhereClause {

}

But I'm not sure how to structure/annotate its properties. A <Where> element can have many different types of child elements (<Eq>, <BeginsWith>, <Contains> etc. Let's ignore <And> and <Or> for now), but not more than one. Eq and BeginsWith are each individually valid children of Where, but it can't be like this:

<Where>
    <Eq>...</Eq>
    <BeginsWith>...</BeginsWith>
</Where>

without nesting the <Eq> and <BeginsWith> in an <Or> element.

My first thought was to create an AbstractSpComparison class with the <FieldRef> and <Value> elements common to all comparisons:

@XmlAccessorType(XmlAccessType.FIELD)
public abstract class AbstractSpComparison {

    @XmlElement
    private SpFieldRef fieldRef;

    @XmlElement
    private SpValue value;

    ...
}

then have SpEqualsComparison extend it:

@XmlType(name="Eq")
public class SpEqualsComparison extends AbstractSpComparison {

}

However, using the abstract class in SpWhereClause leaves me unable to control the name of the child element. This:

@XmlElement
private AbstractSpComparison comparison;

Results in this:

<Where>
    <comparison>...</comparison>
</Where>

instead of this:

<WHERE>
    <Eq>...</Eq>
</WHERE>

Why isn't the "Eq" name from SpEqualsComparison being used to name the comparison element? What's the right way to handle a situation like that?

I considered just having every possible child element type as a property of SpWhereClause, and only set the one I need (with some validation logic somewhere), but that seems unnecessarily verbose.

If it matters, I'm using a Spring OXM Jaxb2Marshaller.

1条回答
地球回转人心会变
2楼-- · 2019-07-22 02:03

There are a couple of different options you could do:

Option #1 - @XmlElementRef

If everything extends a common super class (i.e. AbstractSpComparison) then you could leverage the @XmlElementRef annotation. When you do this the child element will correspond to the value of the @XmlRootElement annotation on the class of the referenced object.

@XmlElementRef
private AbstractSpComparison comparison;

For More Information

I have written more about this approach on my blog:

Option #2 - @XmlElements

If everything doesn't extend a common super class then you can leverage the @XmlElements annotation where you explicitly annotate which elements could appear and what class they correspond to:

@XmlElements({
    @XmlElement(name="Eq", type=Eq.class),
    @XmlElement(name="BeginsWith", type=BeginsWith.class)
})
private AbstractSpComparison comparison;

For More Information

I have written more about this approach on my blog:

查看更多
登录 后发表回答