Search code examples
javaxmlgenericsjaxbgeneric-programming

Flexible JAXB XML parsing for generics and a particular type or a list of that type


I am working with a colleague's API. The API returns a Response with a list of objects or just one, singular object. The objects can be of multiple types. The return type is in XML. I am interested in parsing this XML via JAXB to get my classes, ideally in a flexible and generic way.

The following two XML responses are a sample of what I am speaking about.

Sample 1: A response with a list Jobs containing Job object.

<Response>
   <Status>OK</Status>
   <Jobs>
        <Job>
            <ID>J1</ID>
            <Name>job name</Name> 
        </Job>
        <Job>
            <ID>J2</ID>
            <Name>job name</Name> 
        </Job>
    </Jobs>
</Response>

Sample 2: A response with one Job.

<Response>
    <Status>OK</Status> 
    <Job>
        <ID>J123</ID> 
        <Name>job name</Name> 
    </Job>
</Response>

At the moment, I am constructing something as follows:

@XmlRootElement(name="Response")
@XmlAccessorType(XmlAccessType.FIELD)
public class Response {
    @XmlElement(name = "Status")
    protected Status status;
    @XmlAnyElement(lax=true)
    protected Object items;
}

Unmarshalling via

   Response = (Response) unmarshaller.unmarshal(myXmlResponse);

But I'm receiving null in my items when unmarshalling Sample 1. Also, this approach gives me a bad feeling as I'm using Object as a catch-all i.e. expecting both List<Job> and Job type. What am I doing wrong? Is there a better solution? Maybe my response class can have two generics, one for list of items and another for a single item?

An approach in which the singular <Job> is converted to a list of jobs with one element would also be interesting, but I'm not sure that can be a generic without modifying the XML response.


Solution

  • You could do this:

    @XmlRootElement(name = "Response")
    public class Response {
    
        @XmlElement(name ="Status")
        private Status status;
    
        @XmlElements({
                @XmlElement(name = "Job", type = Job.class),
                @XmlElement(name = "Jobs", type = Jobs.class),
        })
        private List<?> jobs;
    }
    

    Then Job would be:

    public class Job {
    
        @XmlElement(name = "ID")
        private String id;
        @XmlElement(name = "Name")
        private String name;
    }
    

    And Jobs:

    public class Jobs {
    
        @XmlElement(name = "Job")
        private List<Job> jobs;
    }
    

    Update to answer on comment:

    This is the cleanest way I could think of for handling these described payloads. The challenge is with the <Jobs></Jobs> being there only some times.

    There is a way to do it without embedded list but it is messier. I will copy it below so you can decide if you like it, or better to get another cleaner solution.

    @XmlRootElement(name = "Response")
    public class Response {
    
        @XmlElement(name ="Status")
        private Status status;
    
        @XmlElement(name = "Job")
        private List<Job> jobs;
    
        @XmlElementWrapper(name = "Jobs")
        @XmlElement(name = "Job")
        private List<Job> jobsWrapped;
    
    }