Osramadus
Osramadus

Reputation: 43

JAXB unmarshal to concrete class without using xsi:type in xml but using actual concrete class name

I wonder if someone can help me with a JAXB problem.

If I have an abstract class with 2 concrete implementations: For example (I have left out most of the markup/xml for brevity):

public abstract class Vehicle{}

public class Car extends Vehicle{}

public class Van extends Vehicle{}

Is there a way to have the xml below unmarshall correctly to the appropriate concrete class

<request>
  <car>...</car>
</request>

rather than the following:

<request>
  <vehicle xsi:type="car"></vehicle>
</request>

The reason I need this is to be backward compatible with our already published API.

Thanks in advance.

Upvotes: 0

Views: 863

Answers (2)

Mikita Berazouski
Mikita Berazouski

Reputation: 1001

I have just answered in russian speaking community on similar question. Probably you looking for something like that:

@XmlElements({
      @XmlElement(name = "car", type = Car.class),
      @XmlElement(name = "van", type = Van.class)
})
public List<Vehicle> getVehicles() {
      return vehicles;
}

Some quick example:

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.*;
import java.io.StringReader;
import java.util.List;

public class Test {

    public static void main(String... args) throws JAXBException {
        String xmldata = "<request><car></car><van></van></request>";
        StringReader reader = new StringReader(xmldata);

        JAXBContext jaxbContext = JAXBContext.newInstance(Request.class);
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();

        Request request = (Request) unmarshaller.unmarshal(reader);

        for (Vehicle object : request.getVehicles()) {
            System.out.println(object.getClass());
        }
    }
}

@XmlRootElement(name = "request")
class Request {
    private List<Vehicle> vehicles;

    @XmlElements({
            @XmlElement(name = "car", type = Car.class),
            @XmlElement(name = "van", type = Van.class)
    })
    public List<Vehicle> getVehicles() {
        return vehicles;
    }

    public void setVehicles(List<Vehicle> vehicles) {
        this.vehicles = vehicles;
    }
}

abstract class Vehicle {
}

class Van extends Vehicle {
}

class Car extends Vehicle {
}

The output will be:

class Car
class Van

UPD:

Update after comment. For single entry it will work anyway just remove List:

@XmlRootElement(name = "request")
class Request {
    private Vehicle vehicles;

    @XmlElements({
            @XmlElement(name = "car", type = Car.class),
            @XmlElement(name = "van", type = Van.class)
    })
    public Vehicle getVehicles() {
        return vehicles;
    }

    public void setVehicles(Vehicle vehicles) {
        this.vehicles = vehicles;
    }
}

Hope this will help.

Upvotes: 3

Alexander Petrov
Alexander Petrov

Reputation: 9492

You can use annotations and annotate the concrete implementations. In this case @XmlType() above Car or Van. This way you will keep your xml generic.

Upvotes: 1

Related Questions