Is it possible to use JAXB to unmarshall xml to a specific Java class based on an attribute of the xml?
<shapes> <shape type="square" points="4" square-specific-attribute="foo" /> <shape type="triangle" points="3" triangle-specific-attribute="bar" /> </shapes>
I would like to have a List of Shape objects containing a triangle and a square, each with their own shape-specific attribute. IE:
abstract class Shape { int points; //...etc } class Square extends Shape { String square-specific-attribute; //...etc } class Triangle extends Shape { String triangle-specific-attribute; //...etc }
I'm currently just putting all attributes in one big "Shape" class and it's less than ideal.
I could get this to work if the shapes were properly named xml elements, but unfortunately I don't have control of the xml I'm retrieving.
Thanks!
JAXB is a spec, specific implementations will provide extension points to do things such as this. If you are using EclipseLink JAXB (MOXy) you could modify the Shape class as follows:
import javax.xml.bind.annotation.XmlAttribute; import org.eclipse.persistence.oxm.annotations.XmlCustomizer; @XmlCustomizer(ShapeCustomizer.class) public abstract class Shape { int points; @XmlAttribute public int getPoints() { return points; } public void setPoints(int points) { this.points = points; } }
Then using the MOXy @XMLCustomizer you could access the InheritancePolicy and change the class indicator field from "@xsi:type" to just "type":
import org.eclipse.persistence.config.DescriptorCustomizer; import org.eclipse.persistence.descriptors.ClassDescriptor; public class ShapeCustomizer implements DescriptorCustomizer { @Override public void customize(ClassDescriptor descriptor) throws Exception { descriptor.getInheritancePolicy().setClassIndicatorFieldName("@type"); } }
You will need to ensure that you have a jaxb.properties file in with you model classes (Shape, Square, etc) with the following entry specifying the EclipseLink MOXy JAXB implementation:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Below is the rest of the model classes:
Shapes
import java.util.ArrayList; import java.util.List; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Shapes { private List<Shape> shape = new ArrayList<Shape>();; public List<Shape> getShape() { return shape; } public void setShape(List<Shape> shape) { this.shape = shape; } }
Square
import javax.xml.bind.annotation.XmlAttribute; public class Square extends Shape { private String squareSpecificAttribute; @XmlAttribute(name="square-specific-attribute") public String getSquareSpecificAttribute() { return squareSpecificAttribute; } public void setSquareSpecificAttribute(String s) { this.squareSpecificAttribute = s; } }
Triangle
import javax.xml.bind.annotation.XmlAttribute; public class Triangle extends Shape { private String triangleSpecificAttribute; @XmlAttribute(name="triangle-specific-attribute") public String getTriangleSpecificAttribute() { return triangleSpecificAttribute; } public void setTriangleSpecificAttribute(String t) { this.triangleSpecificAttribute = t; } }
Below is a demo program to check that everything works:
import java.io.StringReader; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; public class Demo { public static void main(String[] args) throws Exception { JAXBContext jaxbContext = JAXBContext.newInstance(Shapes.class, Triangle.class, Square.class); StringReader xml = new StringReader("<shapes><shape square-specific-attribute='square stuff' type='square'><points>4</points></shape><shape triangle-specific-attribute='triangle stuff' type='triangle'><points>3</points></shape></shapes>"); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); Shapes root = (Shapes) unmarshaller.unmarshal(xml); Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(root, System.out); } }
I hope this helps.
For more information on EclipseLink MOXy see:
EDIT
In EclipseLink 2.2 we're making this easier to configure, check out the following article for more information:
The annotation @XmlElements enables you to specify which tag corresponds with which subclass.
@XmlElements({ @XmlElement(name="square", type=Square.class), @XmlElement(name="triangle", type=Triangle.class) }) public List<Shape> getShape() { return shape; }
Also see javadoc for @XmlElements
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With