Marshalling/Unmarshalling Java superclass and subclasses using JAXB - java

I've been experimenting with JAXB tutorials and have managed to get code working that generates an XML file from a Java object and then is able to use the XML to generate a Java object. At the moment it reads multiple instances of the same class to create an XML file similar to the one below
<Car>
<regplate>TR54</regplate>
<colour>red</colour>
<energyrating>5</energyrating>
</Car>
<Car>
<regplate>BN04 THY</regplate>
<colour>yellow</colour>
<energyrating>3</energyrating>
</Car>
<Car>
<regplate>BN05 THY</regplate>
<colour>yellow</colour>
<energyrating>5</energyrating>
</Car>
I would like to be able to use the JAXB technology to work with subclasses. For example: Say I have a Car, Van and Bicycle objects that are subclasses of Vehicle. Is it possible for me to manipulate my JAXB class to write an XML file that would produce something similar to this? I have provided the code I am working with below.
<Vehicle>
<Car>
<regplate>TR54</regplate>
<colour>red</colour>
<energyrating>5</energyrating>
</Car>
<Van>
<regplate>MN05 RFD</regplate>
<colour>red</colour>
<energyrating>5</energyrating>
</Van>
<Car>
<regplate>ZX54 UJK</regplate>
<colour>red</colour>
<energyrating>1</energyrating>
</Car>
</Vehicle>
Main Class
package basictransport2;
public class Main
{
public static void main(String[] args)
{
JAXB parser = new JAXB();
parser.marshall();
//parser.unmarshallList();
}
}
Vehicle Class
package basictransport2;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
//#XmlRootElement(name = "Vehicle")
public class Vehicle
{
private int ownerId;
public Vehicle(int ownerId)
{
this.setOwnerId(ownerId);
}
//#XmlElement (name = "Owner ID")
public int getOwnerId()
{
return ownerId;
}
public void setOwnerId(int ownerId)
{
this.ownerId = ownerId;
}
public int getEnergyRating()
{
return (Integer) null;
}
public String getColour()
{
return null;
}
public String getRegPlate()
{
return null;
}
}
Car Class
package basictransport2;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
//#XmlRootElement(name = "Car")
public class Car extends Vehicle
{
private String regPlate;
private int energyRating;
private String colour;
public Car(String regPlate, int energyRating, String colour, int ownerId)
{
super(ownerId);
this.regPlate = regPlate;
this.energyRating = energyRating;
this.colour = colour;
}
public Car(int ownerId)
{
super(ownerId);
}
//#XmlElement (name = "Registration")
public String getRegPlate()
{
return regPlate;
}
public void setRegPlate(String regPlate)
{
if(this.regPlate == null)
{
this.regPlate = regPlate;
}
}
//#XmlElement (name = "Energy Rating")
public int getEnergyRating()
{
return energyRating;
}
public void setEnergyRating(int energyRating)
{
this.energyRating = energyRating;
}
//#XmlElement (name = "Colour")
public String getColour()
{
return colour;
}
public void setColour(String colour)
{
this.colour = colour;
}
}
JAXB Class
package basictransport2;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
public class JAXB
{
public void marshall()
{
try
{
List<Vehicle> vehicleList = new ArrayList<Vehicle>();
vehicleList.add(new Car("SG09 TYH", 4, "Yellow", 1));
vehicleList.add(new Car("XX09 VVV", 3, "Red", 2));
vehicleList.add(new Car("BL09 TYZ", 4, "Blue", 3));
Garage listOfVehicles = new Garage();
listOfVehicles.setListOfVehicles(vehicleList);
JAXBContext context = JAXBContext.newInstance(Garage.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(listOfVehicles, System.out);
marshaller.marshal(listOfVehicles, new File("src\\data\\listcar.xml"));
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
}
public void unmarshall()
{
try
{
JAXBContext context = JAXBContext.newInstance(Garage.class);
Unmarshaller unmarhsaller = context.createUnmarshaller();
Garage listOfVehicles = (Garage)unmarhsaller.unmarshal(new File("src\\data\\listcar.xml"));
System.out.println("List Car information");
for(Vehicle vehicle : listOfVehicles.getListOfVehicles())
{
System.out.println("Reg Plate: " + vehicle.getRegPlate());
System.out.println("Energy Rating: " + vehicle.getEnergyRating());
System.out.println("Colour: " + vehicle.getColour());
System.out.println("================");
}
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
}
}
List class
package basictransport2;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlType;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name="Vehicle")
public class Garage
{
#XmlElements
({
#XmlElement(name = "Car", type = Car.class, required = false)
})
private List<Vehicle> vehicleCollection = new ArrayList<Vehicle>();
public List<Vehicle> getListOfVehicles()
{
return vehicleCollection;
}
public void setListOfVehicles(List<Vehicle> listOfVehicles)
{
this.vehicleCollection = listOfVehicles;
}
}

Thanks everyone for your input. I used feedback from all your answers but ultimately it was a combination of them that worked which is why I created a seperate answer for anyone who may have this problem in the future.
To get this to work I had to ensure that all getter methods within the super and sub classes being marhsalled/unmarshalled were annotated with #XmlElement. This would determine the XML tag for the corresponding variable.
#XmlElement (name = "OwnerID")
public int getOwnerId()
{
return ownerId;
}
The superclass had to be annotated with #XmlSeeAlso to bind the subclasses to it. i.e In my code RoadVehicle was the superclass and both the Car and Van classes extended it.
#XmlSeeAlso({Car.class, Van.class})
public class Vehicle
{
With the super and subclasses now annotated the only other class that required annotations was the list class (Garage in my code). The changes here would determine what the XML tags were populated with.
The root XML tag was set by applying the #XmlRootElement annotation to the top of the class. i.e. "Vehicle" would be the root XML tag in my example.
#XmlRootElement(name = "Vehicle")
public class Garage
{
Finally an #XmlElements list had to be declared with an #XmlElements annotation for each sub class that required an XML tag with the name supplying the name of the XML tag. This list had to be declared above the getter method for the collection.
#XmlElements
({
#XmlElement(name = "Car", type = Car.class, required = false),
#XmlElement(name = "Van", type = Van.class, required = false)
})
public List<Vehicle> getListOfVehicles()
{
return vehicleCollection;
}

you are on right track. May something below will help
#XmlRootElement(name = "car")
public class Car extends BasicType{
}
#XmlRootElement(name = "van")
public class Van extends BasicType{
}
#XmlRootElement(name = "vehicle")
public class Vehicle {
List<BasicType> basicType;
}

The simplest solution is to have different subclasses for cars and vans, even it they don't add anything to the base classes. Then, the root element class contains a list of the base class, with element QNames identifying the actual class.
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "Vehicle")
public class Vehicle {
#XmlElements({
#XmlElement(name = "Car", type = Car.class, required = false),
#XmlElement(name = "Van", type = Van.class, required = false)
})
protected List carOrVan;
public List getCarOrVan() {
if (carOrVan == null) {
carOrVan = new ArrayList();
}
return this.carOrVan;
}
}
Here's the base class and the subclasses:
public class Basic {
private String regplate;
private String color;
private String energyrating;
public String getRegplate(){ return regplate; }
public void setRegplate( String v ){ regplate = v; }
public String getColor(){ return color; }
public void setColor( String v ){ color = v; }
public String getEnergyrating(){ return energyrating; }
public void setEnergyrating( String v ){ energyrating = v; }
}
public class Car extends Basic {}
public class Van extends Basic {}
This will go smoothly if cars and vans develop into distinct subclasses.

Related

JAXB XMLAdapter: Is there a way to convert this method into JAXB XmlAdapter

I have a JSON file that I am trying to convert into XML using the JAXB annotation approach. Everything is working fine now and I able to convert the JSON to XML. Now I am trying to refactor the code a little bit so that my class would look clean. Hence, I am trying to remove the method which is present in my class and make it JAXB XMLAdapter so that it can be reused by other classes.
Basically I would like to move the XMLSupport method from CarInfo class to XMLAdapter. I am not sure how to populate the CarInfo objects when I move them to the XMLAdapter.
Following is my JSON file (it has been modified for simplicity purpose):
{
"brand": "Ferari",
"build": "Italy",
"engine": "Mercedes",
"year": "2021"
}
Following is the XML that I expect JAXB to provide: (Observe the carInfo tag which is not present in JSON but I need in XML to match the standard XSD)
<?xml version="1.0"?>
<Car>
<brand>Ferari</brand>
<build>Italy</build>
<carinfo>
<engine>Mercedes</engine>
<year>2021</year>
</carinfo>
</Car>
Following are the classes that I have: (Tha Car class that matches the JSON elements)
#XmlAccessorType(XmlAccessType.FIELD)
#XmlTransient
#XmlSeeAlso({MyCar.class});
public class Car{
private String brand;
private String build;
#XmlTransient
private String engine;
#XmlTransient
private String year;
//Getter, Setters and other consturctiores ommited
}
Following is MYCar class that builds the XML by adding the carInfo tag:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "Car")
#XmlType(name = "Car", propOrder = {"brand","build", "carInfo"})
public class MyCar extends Car{
#XmlElement(name="carInfo")
private CarInfo carInfo;
public MyCar xmlSupport() {
if(carInfo == null){
carInfo = new Carinfo();
}
carInfo.setEngine(getEngine);
carInfo.setYear(getYear());
return this;
}
}
Following is my CarInfo class which acts as a helper to build the additional tag around MyCar class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(propOrder = {"engine","year"})
public class Carinfo{
private String engine;
private String year;
//Getter, Setters and other consturctiores ommited
}
Following is my Main class which actually builds the XML by using the JAXBCOntext
public class Main{
public static void main(String[] args){
JAXBContext context = JAXBContext.newInstance(MyCar.class);
Marshaller mar = context.createMarshaller();
mar.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
mar.marshal((MyCar).xmlSupport(), System.out);
System.out.println("-----------------");
}
}
Now coming back to my main question:
As we can see from MyCar class I have the XMLSupport method which is actually populating the CarInfo objects and then using that method I am creating the XML. Is there a way I can move this to XMLAdapter?
I tried creating the XMLAdapter but I am not sure how can I populate the CarInfo objects from the adapter:
public class MyCar extends Car{
#XmlElement(name="carInfo")
#XmlJavaTypeAdapter(ExtensionAdapter.class)
#XmlElement(name = "carInfo")
private CarInfo carInfo;
}
Following is my Adapter class I've tried:
public class ExtensionAdapter extends XmlAdapter<CarInfo, CarInfo> {
#Override
public CarInfo unmarshal(CarInfo valueType) throws Exception {
System.out.println("UN-MARSHALLING");
return null;
}
#Override
public CarInfo marshal(CarInfo boundType) throws Exception {
System.out.println("MARSHALLING");
System.out.println(boundType);
//I get boundType as NULL so I am not sure how to convert the xmlSupport Method to Adapter so I can use this adapter with multiple class
return null;
}
}
You don't need any adapters, you just need a well-defined POJO.
The trick is using getters and setters, not field access, so we can do delegation, and then use #JsonIgnore and #XmlTransient to control which getter/setter methods are used for JSON vs XML.
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
#XmlRootElement(name = "Car")
#XmlType(propOrder = { "brand", "build", "carinfo" })
#JsonPropertyOrder({ "brand", "build", "engine", "year" })
public final class Car {
#XmlType(propOrder = { "engine", "year" })
public static final class Info {
private String engine;
private String year;
public String getEngine() {
return this.engine;
}
public void setEngine(String engine) {
this.engine = engine;
}
public String getYear() {
return this.year;
}
public void setYear(String year) {
this.year = year;
}
#Override
public String toString() {
return "Info[engine=" + this.engine + ", year=" + this.year + "]";
}
}
private String brand;
private String build;
private Info carinfo;
public Car() {
// Nothing to do
}
public Car(String brand, String build, String engine, String year) {
this.brand = brand;
this.build = build;
this.carinfo = new Info();
this.carinfo.setEngine(engine);
this.carinfo.setYear(year);
}
public String getBrand() {
return this.brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getBuild() {
return this.build;
}
public void setBuild(String build) {
this.build = build;
}
#JsonIgnore // For XML, not JSON
public Info getCarinfo() {
if (this.carinfo == null)
this.carinfo = new Info();
return this.carinfo;
}
public void setCarinfo(Info info) {
this.carinfo = info;
}
#XmlTransient // For JSON, not XML
public String getEngine() {
return getCarinfo().getEngine();
}
public void setEngine(String engine) {
getCarinfo().setEngine(engine);
}
#XmlTransient // For JSON, not XML
public String getYear() {
return getCarinfo().getYear();
}
public void setYear(String year) {
getCarinfo().setYear(year);
}
#Override
public String toString() {
return "Car[brand=" + this.brand + ", build=" + this.build + ", carinfo=" + this.carinfo + "]";
}
}
Test
Car car = new Car("Ferari", "Italy", "Mercedes", "2021");
// Generate JSON
ObjectMapper jsonMapper = new ObjectMapper();
jsonMapper.enable(SerializationFeature.INDENT_OUTPUT);
String json = jsonMapper.writeValueAsString(car);
// Generate XML
JAXBContext jaxbContext = JAXBContext.newInstance(Car.class);
Marshaller xmlMarshaller = jaxbContext.createMarshaller();
xmlMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
String xml;
try (StringWriter writer = new StringWriter()) {
xmlMarshaller.marshal(car, writer);
xml = writer.toString();
}
// Print generated results
System.out.println(car);
System.out.println(json);
System.out.println(xml);
// Parse JSON
Car carFromJson = jsonMapper.readValue(json, Car.class);
System.out.println(carFromJson);
// Parse XML
Unmarshaller xmlUnmarshaller = jaxbContext.createUnmarshaller();
Car carFromXml = xmlUnmarshaller.unmarshal(new StreamSource(new StringReader(xml)), Car.class).getValue();
System.out.println(carFromXml);
Outputs
Car[brand=Ferari, build=Italy, carinfo=Info[engine=Mercedes, year=2021]]
{
"brand" : "Ferari",
"build" : "Italy",
"engine" : "Mercedes",
"year" : "2021"
}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Car>
<brand>Ferari</brand>
<build>Italy</build>
<carinfo>
<engine>Mercedes</engine>
<year>2021</year>
</carinfo>
</Car>
Car[brand=Ferari, build=Italy, carinfo=Info[engine=Mercedes, year=2021]]
Car[brand=Ferari, build=Italy, carinfo=Info[engine=Mercedes, year=2021]]
As you can see, the generated JSON and XML is exactly what you wanted, and the last two lines of output shows that parsing works as well.

How to read xsi:type with java annotations

I want to read in a xml-file based on jaxb to my objectoriented structure.
Lets say this is my xml-file:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<children xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<child xsi:type="girl">
<age>12</age>
<isdancing>true</isdancing>
</child>
<child xsi:type="boy">
<age>10</age>
<issoccerplayer>true</issoccerplayer>
</child>
</children>
children is some kind of wrapper element including multiple child elements. A child can either be a boy or a girl specified by xsi:type. These two classes have some elements in common (like age) and some different (excluding) elements (like isdancing or issoccerplayer)
To read the file, i have this method:
public static void main( String[] args ) throws JAXBException
{
JAXBContext jaxbContext;
jaxbContext = JAXBContext.newInstance(Children.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
File file = new File("C:/test.xml");
if (!file.exists()) System.out.println("File does not exist");
Children children = (Children) jaxbUnmarshaller.unmarshal(file);
System.out.println(children.toString());
}
My Children class looks like this:
#XmlRootElement(name="children")
#XmlAccessorType(XmlAccessType.FIELD)
public class Children {
#XmlElement(name="child")
private List<Child> childrenList;
public List<Child> getChildren() { return childrenList; }
public void setChildren(List<Child> children) {this.childrenList = children;}
#Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
}
My Child class looks like this:
#XmlAccessorType(XmlAccessType.FIELD)
public class Child {
#XmlAttribute(name="xsi:type")
private XsiType xsiType;
private int age;
#XmlElement(name = "isdancing")
private boolean isDancing;
#XmlElement(name = "issoccerplayer")
private boolean isSoccerPlayer;
//Getter and setter for all fields
#Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
}
And my XsiType class looks like this:
#XmlAccessorType(XmlAccessType.FIELD)
public class XsiType {
#XmlAttribute(name="xsi:type")
private String name;
#XmlValue
private String value;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getValue() { return value;
public void setValue(String value) { this.value = value; }
}
In my pom.xml i have included the following dependencies:
<dependencies>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
</dependencies>
My problem is now, that the output is ok, but the element xsiType of Child-class is always null or otherwise it ends up in IllegalAnnotationExceptions, which are related to XmlTest.model.Child.xsiType
So i expect there is a mistake by setting any kind of #Xml-Annotation. Can somebody help me by finding the mistake?
The target is to iterate of the list of children and decide at runtime (based on the xsiType), if this is a girl or a boy.
Thanks
You don't need your XsiType class.
You can just use String instead.
In your Child class
the xsiType attribute should look like this.
#XmlAttribute(name = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
private String xsiType;
Notice: in the #XmlAttribute annotation
use name = "type" (without the prefix xsi:)
specify the namespace parameter as given in your XML
by xmlns:xsi="..."
By the way:
Instead of typing the string "http://www.w3.org/2001/XMLSchema-instance"
you should better use the constant
XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI.
So your improved code would like this:
#XmlAttribute(name = "type", namespace = XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI)
private String xsiType;
xsi type is usually used to express references to concrete types. Jaxb can use xsi types without further workarounds.
Create a Boy and a Girl class that extend Children. (You might need to adjust the type names with #XmlType). With that, all elements with xsi:type=Girl will be bound to the class Girl
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({ Boy.class, Girl.class }) // Either use #XmlSeeAlso to register classes in the JaxbContext
// or add them to the context directly
public class Child {
private int age;
#XmlElement(name = "isdancing")
private boolean isDancing;
#XmlElement(name = "issoccerplayer")
private boolean isSoccerPlayer;
// Getter and setter for all fields
}
#XmlType(name = "boy") // can be omitted if default value matches with the default value
public class Boy extends Child {
}
#XmlType(name = "girl")
public class Girl extends Child {
}
Complete selfcontained example:
package jaxb;
import java.io.File;
import java.io.StringReader;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlType;
public class Inheritance {
public static void main(String[] args) throws JAXBException {
JAXBContext jaxbContext;
jaxbContext = JAXBContext.newInstance(Children.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
String x = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\r\n"
+ " <children xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\r\n"
+ " <child xsi:type=\"girl\">\r\n" + " <age>12</age>\r\n"
+ " <isdancing>true</isdancing>\r\n" + " </child>\r\n"
+ " <child xsi:type=\"boy\">\r\n" + " <age>10</age>\r\n"
+ " <issoccerplayer>true</issoccerplayer>\r\n" + " </child>\r\n" + " </children>";
Children children = (Children) jaxbUnmarshaller.unmarshal(new StringReader(x));
System.out.println(children.getChildren().toString());
}
#XmlRootElement(name = "children")
#XmlAccessorType(XmlAccessType.FIELD)
public static class Children {
#XmlElement(name = "child")
private List<Child> childrenList;
public List<Child> getChildren() {
return childrenList;
}
public void setChildren(List<Child> children) {
this.childrenList = children;
}
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({ Boy.class, Girl.class })
public static class Child {
private int age;
#XmlElement(name = "isdancing")
private boolean isDancing;
#XmlElement(name = "issoccerplayer")
private boolean isSoccerPlayer;
// Getter and setter for all fields
}
#XmlType(name = "boy")
public static class Boy extends Child {
}
#XmlType(name = "girl")
public static class Girl extends Child {
}
}
Clean solution for second approach (based on separate class-files):
public class App
{
public static void main(String[] args) throws JAXBException
{
JAXBContext jaxbContext = JAXBContext.newInstance(Children.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
File file = new File("C:/test2.xml");
Children children = (Children) jaxbUnmarshaller.unmarshal(file);
for (Child c : children.getChildren()) {
if (c instanceof Boy) {
System.out.println(((Boy)c).toString());
} else if (c instanceof Girl){
System.out.println(((Girl)c).toString());
}
}
}
}
Children.java
#XmlRootElement(name="children")
#XmlAccessorType(XmlAccessType.FIELD)
public class Children {
#XmlElement(name="child")
private List<Child> childrenList;
public List<Child> getChildren() { return childrenList; }
public void setChildren(List<Child> children) {this.childrenList = children;}
#Override
public String toString() { return ReflectionToStringBuilder.toString(this); }
}
Boy.java
#XmlType(name="boy")
public class Boy extends Child {
#XmlElement(name = "issoccerplayer")
private boolean isSoccerPlayer;
public boolean isSoccerPlayer() { return isSoccerPlayer; }
public void setSoccerPlayer(boolean isSoccerPlayer) { this.isSoccerPlayer = isSoccerPlayer; }
#Override
public String toString() { return ReflectionToStringBuilder.toString(this); }
}
Girl.java
#XmlType(name="girl")
public class Girl extends Child {
#XmlElement(name = "isdancing")
private boolean isDancing;
public boolean isDancing() { return isDancing; }
public void setDancing(boolean isDancing) { this.isDancing = isDancing; }
#Override
public String toString() { return ReflectionToStringBuilder.toString(this); }
}
Child.java
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({ Boy.class, Girl.class })
public abstract class Child {
private int age;
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
The output should be:
de.home.myproject.XmlTest.model.Girl#12edcd21[isDancing=true,age=12]
de.home.myproject.XmlTest.model.Boy#27bc2616[isSoccerPlayer=true,age=10]

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.
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.
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.

Java Unmarshal list of objects with a class wrapper with JAXB

From an XQuery performed by BaseX server I get a result like that:
<ProtocolloList>
<protocollo>
<numero>1</numero>
<data>2014-06-23</data>
<oggetto/>
<destinatario/>
<operatore/>
</protocollo>
...
</ProtocolloList>
And I need to convert this result in a List of Protocollo objects with JAXB so that I can show them with JList. Thus, following one of the discussions here I've declared the following classes:
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "protocollo")
public class Protocollo {
private int numero;
private String data;
private String oggetto;
private String destinatario;
private String operatore;
public Protocollo(String d, String o, String des, String op) {
this.data = d;
this.oggetto = o;
this.destinatario = des;
this.operatore = op;
}
public Protocollo() {
}
#XmlElement
public int getNumero() {
return numero;
}
public void setNumero(int numero) {
this.numero = numero;
}
#XmlElement
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
#XmlElement
public String getOggetto() {
return oggetto;
}
public void setOggetto(String oggetto) {
this.oggetto = oggetto;
}
#XmlElement
public String getDestinatario() {
return destinatario;
}
public void setDestinatario(String destinatario) {
this.destinatario = destinatario;
}
#XmlElement
public String getOperatore() {
return operatore;
}
public void setOperatore(String operatore) {
this.operatore = operatore;
}
}
and
import java.util.ArrayList;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "ProtocolloList")
public class ProtocolloList {
#XmlElementWrapper(name = "ProtocolloList")
#XmlElement(name = "protocollo")
private ArrayList<Protocollo> ProtocolloList;
public ArrayList<Protocollo> getProtocolloList() {
return ProtocolloList;
}
public void setProtocolloList(ArrayList<Protocollo> protocolloList) {
ProtocolloList = protocolloList;
}
}
and finally I execute the converion like that:
JAXBContext jaxbContext = JAXBContext.newInstance(Protocollo.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
StringReader reader = new StringReader(this.resultXML);
protocolli = (ProtocolloList) unmarshaller.unmarshal(reader);
And I keep on getting this exception:
unexpected element (uri:"", local:"ProtocolloList"). Expected elements are <{}protocollo>
I suppose I'm making some mistakes with annotations.
Can you help?
For your use case you do not need the #XmlElementWrapper annotation. This is because the ProtocolList element corresponds to your #XmlRootElement annotation. Then you need the #XmlElement annotation on the property to grab each of the list items.
#XmlRootElement(name = "ProtocolloList")
public class ProtocolloList {
private ArrayList<Protocollo> ProtocolloList;
#XmlElement(name = "protocollo")
public ArrayList<Protocollo> getProtocolloList() {
return ProtocolloList;
}
}
Note:
By default you should annotate the property. If you want to annotate the fields you should put #XmlAccessorType(XmlAccessType.FIELD) on your class.
UPDATE
You need to make sure your JAXBContext is aware of the root class. You can change your JAXBContext creation code to be the following:
JAXBContext jaxbContext = JAXBContext.newInstance(ProtocolloList.class);

JAXB - Unexpected elements in XML output

I have a class called Building.
It has a list of BuildingBenchAssociation records (List<BuildingBenchAssociation> benches)
BuildingBenchAssociation has a composite id made up of buildingId and benchId
The ID is represented by separate class called BuildingBenchAssociationPKwhich has only two properties - buildingId and benchId
This is the output I get when I marshal a Building instance
<building buildingId="9">
<benches>
DOMRecord(<?xml version="1.0" encoding="UTF-8"?><buildingBenchAssociation><benchId>245865</benchId><buildingId>9</buildingId></buildingBenchAssociation>)
</benches>
<benches>
DOMRecord(<?xml version="1.0" encoding="UTF-8"?><buildingBenchAssociation><benchId>245866</benchId><buildingId>9</buildingId></buildingBenchAssociation>)
</benches>
<benches>
But I don't want DOMRecord(<?xml version="1.0" encoding="UTF-8"?> to appear in the output. Required output is something like this:
<building buildingId="9">
<benches>
<buildingBenchAssociation><benchId>245865</benchId><buildingId>9</buildingId></buildingBenchAssociation>
</benches>
<benches>
<buildingBenchAssociation><benchId>245866</benchId><buildingId>9</buildingId></buildingBenchAssociation>
</benches>
<benches>
What's wrong and how do I correct it? I am using the Eclipselink MOXy library.
Classes for reference:
Class 1
#Entity
#Table(name="building")
#XmlRootElement
public class Building implements Serializable {
....
private List<BuildingBenchAssociation> benchs = new ArrayList<BuildingBenchAssociation>();
#XmlIDREF
#OneToMany(mappedBy="building")
public List<BuildingBenchAssociation> getBenchs() {
return benchs;
}
public void setBenchs(List<BuildingBenchAssociation> benchs) {
this.benchs = benchs;
}
}
Class 2
#Entity
#Table(name="building_bench_rel")
#XmlRootElement
public class BuildingBenchAssociation implements Serializable {
private static final long serialVersionUID = 1L;
private BuildingBenchAssociationPK idx;
private Bench bench;
private Building building;
private byte alertFlags;
private byte status;
public BuildingBenchAssociation() {
idx=new BuildingBenchAssociationPK();
}
#XmlID
#XmlPath(".")
#Id
public BuildingBenchAssociationPK getIdx() {
return this.idx;
}
public void setIdx(BuildingBenchAssociationPK id) {
this.idx = id;
}
#Column(name="ALERT_FLAGS")
public byte getAlertFlags() {
return this.alertFlags;
}
public void setAlertFlags(byte alertFlags) {
this.alertFlags = alertFlags;
}
#Column(name="STATUS", insertable=false, updatable=false)
public byte getStatus() {
return this.status;
}
public void setStatus(byte status) {
this.status = status;
}
#XmlIDREF
#ManyToOne
#JoinColumn(name="BENCH_ID",insertable=false,updatable=false)
public Bench getBench() {
return bench;
}
public void setBench(Bench bench) {
this.bench = bench;
this.idx.setBenchId(bench==null?null:bench.getBenchId());
}
#XmlIDREF
#ManyToOne
#JoinColumn(name="BUILDING_ID",insertable=false,updatable=false)
public Building getBuilding() {
return building;
}
public void setBuilding(Building building) {
this.building = building;
this.idx.setBuildingId(building==null?null:building.getBuildingId());
}
}
Class 3
#Embeddable
#XmlRootElement
public class BuildingBenchAssociationPK implements Serializable {
...
private Integer buildingId;
private Integer benchId;
public BuildingBenchAssociationPK() {
}
#XmlKey
#Column(name="BUILDING_ID")
public Integer getBuildingId() {
return this.buildingId;
}
public void setBuildingId(Integer buildingId) {
this.buildingId = buildingId;
}
#XmlKey
#Column(name="BENCH_ID")
public Integer getBenchId() {
return this.benchId;
}
public void setBenchId(Integer benchId) {
this.benchId = benchId;
}
}
Below is how you can currently map this use case using MOXy. I have opened the following enhancement request to make this use case easier to map:
http://bugs.eclipse.org/407460
REFERENCED OBJECT
Embedded ID (EmployeeId)
Below is an example of an embedded ID class:
import java.math.BigDecimal;
import javax.persistence.*;
import javax.xml.bind.annotation.*;
#Embeddable
#XmlAccessorType(XmlAccessType.FIELD)
public class EmployeeId {
#Column(name="E_ID")
BigDecimal eId;
String country;
}
Class with Embedded ID (Employee)
We want to use the embedded ID class for as the key in an XML relationship. Currently MOXy does not allow this to be done via annotations, so we will leverage the #XmlCustomizer annotation to programmatically modify the metadata.
import java.util.List;
import javax.persistence.*;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;
#Entity
#IdClass(EmployeeId.class)
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#XmlCustomizer(EmployeeCustomizer.class)
public class Employee {
#EmbeddedId
#XmlPath(".")
EmployeeId id;
#OneToMany(mappedBy="contact")
List<PhoneNumber> contactNumber;
}
Customize Mapping Metadata for Employee (EmployeeCustomizer)
In the customizer class we will specify the XPaths for the mappings that compose the key on the embedded class.
import org.eclipse.persistence.config.DescriptorCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;
public class EmployeeCustomizer implements DescriptorCustomizer {
#Override
public void customize(ClassDescriptor descriptor) throws Exception {
descriptor.addPrimaryKeyFieldName("eId/text()");
descriptor.addPrimaryKeyFieldName("country/text()");
}
}
REFERRING OBJECT
PhoneNumber
We are also going to need to programatically add the mapping based on the composite key, so once again we will use the #XmlCustomizer annotation.
import javax.persistence.*;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;
#Entity
#XmlAccessorType(XmlAccessType.FIELD)
#XmlCustomizer(PhoneNumberCustomizer.class)
public class PhoneNumber {
#ManyToOne
#JoinColumns({
#JoinColumn(name="E_ID", referencedColumnName = "E_ID"),
#JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY")
})
Employee contact;
}
Customize Mapping Metadata for PhoneNumber (PhoneNumberCustomizer)
In this customizer we will remove the default mapping, and programatically create the new one based on the composite keys.
import org.eclipse.persistence.config.DescriptorCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.oxm.mappings.XMLObjectReferenceMapping;
public class PhoneNumberCustomizer implements DescriptorCustomizer {
#Override
public void customize(ClassDescriptor descriptor) throws Exception {
descriptor.removeMappingForAttributeName("contact");
XMLObjectReferenceMapping contactMapping = new XMLObjectReferenceMapping();
contactMapping.setAttributeName("contact");
contactMapping.setReferenceClass(Employee.class);
contactMapping.addSourceToTargetKeyFieldAssociation("contact/#eID", "eId/text()");
contactMapping.addSourceToTargetKeyFieldAssociation("contact/#country", "country/text()");
descriptor.addMapping(contactMapping);
}
}
DEMO CODE
The following demo code can be used to demonstrate that everything works:
Demo
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Employee.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("input.xml");
Employee employee = (Employee) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(employee, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<employee>
<eId>10</eId>
<country>Canada</country>
<contactNumber>
<contact eID="10" country="Canada"/>
</contactNumber>
<contactNumber>
<contact eID="10" country="Canada"/>
</contactNumber>
</employee>
FOR MORE INFORMATION
http://wiki.eclipse.org/EclipseLink/Examples/MOXy/JPA/EmbeddedIdClass

Categories

Resources