0

I am trying to unmarshall Xml returned from a rest call into POJOs. However, one call can return different types of documents with different root elements e.g.

<obj1> ... </obj1>
<obj2> ... </obj2>

I unmarshal using a generic function:

private <T> T unmarshal(String xml, Class<T> clazz) {
    JAXBContext jc = JAXBContext.newInstance(clazz);
    return clazz.cast(jc.createUnmarshaller().unmarshal(new StringReader(xml));
}

I've created to separate classes for each of the different roots, but I don't know how I can check the root element type and then call my unmarshall function with the correct type?

if rootElement.equals("obj1")
    Obj1 obj = unmarshal(xml, Obj1.class)
else if rootElement.equals("obj2")
    Obj2 obj = unmarshal(xml, Obj2.class)

Is there a way using JaxB to do this conditional checking on a root element?

Niru
  • 1,407
  • 1
  • 28
  • 47

2 Answers2

0

yes it is possible.

  1. Declare each of the possible root class with @XmlRootElement.
  2. Create JAXBContext with all possible root class like below.

    JAXBContext jc = JAXBContext.newInstance(Class...)

  3. Then,

    Object obj = unmarshal(xml); if(obj instanceof Root1) { // cast to Root1 object } else obj instanceof Root2) { // cast to Root2 object }

ulab
  • 1,079
  • 3
  • 15
  • 45
0

I don't know if there is a better way of doing this, but I didn't find one. To solve this issue I created an object that contains both types of root elements:

@Data
public class compositionObject {
private Obj1 obj1;
private Obj2 obj2;

public compositionObject(final Object obj) {
    if(obj instanceof Obj1) {
        this.obj1 = obj1;
    } else if(obj instanceof Obj2) {
        this.obj2 = obj2;
    } else {
        throw new IllegalArgumentExcepion("not supported");
    }
}

To unmarshall in a semi generic manner:

private Object unmarshal(String xml, Class<?>... clazzes) {
    JAXBContext jc = JAXBContext.newInstance(clazzes);
    return clazz.cast(jc.createUnmarshaller().unmarshal(new StringReader(xml));
}

Using @XmlRegistry with @XmlElementDecl does not give me the intended behavior since it would return JAXBElement<Obj1> instead of JAXBElement<CompositionObject>. The following did not work:

private final static QName OBJ1_QNAME = new QName("", "obj1");
private final static QName COMP_OBJ_QNAME = new QName("", "compositionobj");

@XmlElementDecl(namespace = "",  name = "obj1")
public JAXBElement<CompositionObject> createObj1(final Obj1 value) {
    final CompositionObject compObj = new CompositionObject();
    comPbj.setObj1(value);
    return new JAXBElement<CompositionObject>(COMP_OBJ_QNAME, CompositionObject.class, null, value);
}

The question: @XmlRegistry - how does it work? answers why an @XmlRegistry can't be used in that way.

Community
  • 1
  • 1
Niru
  • 1,407
  • 1
  • 28
  • 47
  • `@XmlRootElement` itself is enough that you get right object instance after unmarshalling. I think the problem is that you have not delcared them as `@XmlRootElement`. May be you can explain in detail why you cant cast to root element object directly using point 3 above in my answer? – ulab Feb 01 '17 at 10:36
  • Both Obj1 and Obj2 model classes are annotated with '@XmlRootElement'. I suppose the problem statement was a bit ambiguous. I wanted to write a custom parse logic such that it could take multiple '@XmlRootElement' and convert them into a single object. I wanted to do this because the call I was making to an external REST service can return different types of objects for the same call. – Niru Feb 01 '17 at 22:11