1

I have a situation in which a Java object contains a generic Payload<T> that needs to be marshalled into xml. So given the following classes:

AbstractBox

@XmlTransient
public abstract class AbstractBox {
    String type = this.getClass().getSimpleName();
    String name = this.getClass().getCanonicalName();

    // setters/getters snipped

    public abstract String saySomething();
}

SmallBox

@XmlRootElement(name="small-box")
@XmlType(name="small-box")
public class SmallBox extends AbstractBox {

    @XmlElement
    public String getMsg() {
        return saySomething();
    }
    public void setMsg(String msg) {
        // do nothing
    }

    @Override
    public String saySomething() {
        return "I'm a small box";
    }
}

Payload

@XmlTransient
public class Payload<T> {
    private T payload;

    public T getPayload() {
        return payload;
    }

    public void setPayload(T payload) {
        this.payload = payload;
    }
}

and some code like this:

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class JaxbAnnotationsTest<P>
{
    String name;
    int age;
    int id;
    Payload<P> payload;

    // setters and getters snipped

    public static void main(String[] args) {
        JaxbAnnotationsTest<AbstractBox> example = new JaxbAnnotationsTest<AbstractBox>();
        example.setName("Brion");
        example.setId(100);
        example.setAge(34);
        Payload<AbstractBox> object = new Payload<AbstractBox>();
        object.setPayload(new SmallBox());
        example.setPayloadContainer(object);

        try {
            XmlMapper xmapper = new XmlMapper();
            xmapper.writeValue(System.out, example);
        } catch (Exception ex) {
            System.err.println("Damn..." + ex.getMessage());
        }
    }

I expect to see:

<JaxbAnnotationsTest>
  <name>Brion</name>
  <age>34</age>
  <id>100</id>
  <payloadContainer>
    <small-box>
      <type>SmallBox</type>
      <name>sandbox.xml.jaxb.annotations.SmallBox</name>
      <msg>I'm a small box</msg>
    </small-box>
  </payloadContainer>
</JaxbAnnotationsTest>

but instead I get:

<JaxbAnnotationsTest>
  <name>Brion</name>
  <age>34</age>
  <id>100</id>
  <payloadContainer>
    <payload>
      <type>SmallBox</type>
      <name>sandbox.xml.jaxb.annotations.SmallBox</name>
      <msg>I'm a small box</msg>
    </payload>
  </payloadContainer>
</JaxbAnnotationsTest>

I've tried using @XmlType on the concrete subclass to change payload to small-box but that didn't work either. If I remove the Payload<P> object and simply have a class member payload of generic type P then the paymentContainer tag goes away, but payload remains and does not use the small-box name I've specified.

Is there a way for me to force JAXB (any implementation) to set the tag to the name specified in the subclass instead of the generic type property?

Update: The selected answer provides the solution but I wanted to follow up in the question as well. My problem was two-fold:

  1. I was using @XmlTransient on the Payload<T> class and needed to instead use an @XmlAnyElement annotation on the setPayload(T payload) method (though I suspect it doesn't matter which method of the setter/getter pair is annotated as long as only one has the annotation).
  2. I was using Jackson 2's JacksonJaxbXmlProvider which is an incomplete JAXB implementation that was ignoring the @XmlRootElement of the element used as the value of the @XmlAnyElement-annotated property.

Changing my JAXB provider to use the Java 6/7 built-in JAXB provider generated the output I expected.

1 Answer 1

3

Your Payload class expects any bean type as a property, so JAXB doesn't know how to marshall that particular object (in this case SmallBox). Since you need to keep the generic property in Payload the solution should be

  1. Remove @XmlTransient annotation to make these types available for marshalling (I am wondering how it worked with this annotation as you mentioned)
  2. Annotate setPayload in Payload class with @XmlAnyElement as follows

public class Payload { private T payload;

public T getPayload() {
    return payload;
}

@XmlAnyElement
public void setPayload(T payload) {
    this.payload = payload;
}

}

@XmlAnyElement javadoc says

Maps a JavaBean property to XML infoset representation and/or JAXB element.

This means any known bean type (annotated with @XmlRootElement) which is passed into setPayload() will be resolved by the JAXB to their corresponding type, here that bean is SmallBox, otherwise to a default element type ( i think it should be the default implementation of org.w3c.dom.Element). After this change it will marshall the JaxbAnnotationsTest nicely to following xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<jaxbAnnotationsTest>
    <age>34</age>
    <id>100</id>
    <name>Brion</name>
    <payloadContainer>
        <small-box>
            <name>org.mycode.SmallBox</name>
            <type>SmallBox</type>
            <msg>I'm a small box</msg>
        </small-box>
    </payloadContainer>
</jaxbAnnotationsTest>

Hope that will help you.

Sign up to request clarification or add additional context in comments.

4 Comments

I have tried your solution and it does not marshall the objects any differently. I am using the Jackson 2 JAXB library to do both JSON and XML marshalling. What are you using to get this sample code to produce your output?
Oh.. You could have mentioned that :) i haven't used any library I tried using JAXB standard JDK implementation. It works fine in both JDK 6 & 7
Interesting. I will try the embedded JAXB implementation and EclipseLink MOXy because I know Jackson does not fully implement the JAXB specification. Thank you!
I had to do a little more: @XmlAnyElement(lax = true) and then on Payload also use @XmlSeeAlso({SmallBox.class})

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.