Java/JAXB: Unmarshall Xml to specific subclass based on an attribute
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:
- http://www.eclipse.org/eclipselink/moxy.php
EDIT
In EclipseLink 2.2 we're making this easier to configure, check out the following article for more information:
- http://bdoughan.blogspot.com/2010/11/jaxb-and-inheritance-moxy-extension.html
Java/JAXB: Unmarshall XML attributes to specific Java object attributes
How about?
Introduce a common super class called Options:
import javax.xml.bind.annotation.XmlAttribute;
public abstract class Options {
private String name;
@XmlAttribute
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Then on your class with the list of options (Configuration in this example), specify an @XmlJavaTypeAdapter on that property:
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlRootElement
public class Configuration {
private List<Options> section = new ArrayList<Options>();
@XmlJavaTypeAdapter(OptionsAdapter.class)
public List<Options> getSection() {
return section;
}
public void setSection(List<Options> section) {
this.section = section;
}
}
The XmlAdapter will look something like this:
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class OptionsAdapter extends XmlAdapter<AdaptedOptions, Options> {
@Override
public Options unmarshal(AdaptedOptions v) throws Exception {
if("default_options".equals(v.name)) {
DefaultOptions options = new DefaultOptions();
options.setName(v.getName());
options.setDefaultPort(Integer.valueOf(v.map.get("default_port")));
options.setLogLevel(v.map.get("log_level"));
return options;
} else {
CustomOptions options = new CustomOptions();
options.setName(v.getName());
options.setCompatibility(v.map.get("compatibility"));
options.setMemory(v.map.get("memory"));
return options;
}
}
@Override
public AdaptedOptions marshal(Options v) throws Exception {
AdaptedOptions adaptedOptions = new AdaptedOptions();
adaptedOptions.setName(v.getName());
if(DefaultOptions.class == v.getClass()) {
DefaultOptions options = (DefaultOptions) v;
adaptedOptions.map.put("default_port", String.valueOf(options.getDefaultPort()));
adaptedOptions.map.put("log_level", options.getLogLevel());
} else {
CustomOptions options = (CustomOptions) v;
adaptedOptions.map.put("compatibility", options.getCompatibility());
adaptedOptions.map.put("memory", options.getMemory());
}
return adaptedOptions;
}
}
AdaptedOptions looks like:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlValue;
public class AdaptedOptions extends Options {
@XmlAttribute String name;
@XmlElement List<Value> value = new ArrayList<Value>();
Map<String, String> map = new HashMap<String, String>();
public void beforeMarshal(Marshaller marshaller) {
for(Entry<String, String> entry : map.entrySet()) {
Value aValue = new Value();
aValue.name = entry.getKey();
aValue.value = entry.getValue();
value.add(aValue);
}
}
public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
for(Value aValue : value) {
map.put(aValue.name, aValue.value);
}
}
private static class Value {
@XmlAttribute String name;
@XmlValue String value;
}
}
JAXB: Unmarshall to different classes based on an element's attribute value
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
You could use MOXy's @XmlDescriminatorNode
/@XmlDescriminatorValue
JAXB extension.
Base
@XmlDiscriminatorNode("@name")
public abstract class Base {
}
Person
@XmlDiscriminatorValue("person")
public class Person extends Base {
}
Vehicle
@XmlDiscriminatorValue("vehicle")
public class Vehicle extends Base {
}
For More Information
- http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-moxy-extension.html
- http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
Below is an answer to a similar question that uses an older version of the MOXy API:
- Java/JAXB: Unmarshall Xml to specific subclass based on an attribute
Unmarshalling to a subclass with JAXB
One of my previous answers to a similar question may help here. Essentially it is using the @XmlDescrimatorNode in EclipseLink JAXB (MOXy). Note I'm the MOXy tech lead.
- Java/JAXB: Unmarshall Xml to specific subclass based on an attribute
You could also do this with an XmlAdapter. AdaptedEntity would have all the properties from Entity and it's subclasses.
- JAXB @XmlElements, different types but same name?
Overwriting attributes in subclasses with jaxb
Are you looking to use the path attribute as the inheritance indicator? If so the following will help:
- Stack Overflow: Java/JAXB: Unmarshall Xml to specific subclass based on an attribute
- Blog Post: http://bdoughan.blogspot.com/2010/11/jaxb-and-inheritance-moxy-extension.html
I'm still not 100% sure I understand your use case but what about the following:
Stereotypes
import java.util.List;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Stereotypes {
private List<AbstractStereotype> sterotypes;
@XmlElementRef
public List<AbstractStereotype> getSterotypes() {
return sterotypes;
}
public void setSterotypes(List<AbstractStereotype> sterotypes) {
this.sterotypes = sterotypes;
}
}
AbstractStereotype
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlSeeAlso;
@XmlSeeAlso({Stereotype1.class, Stereotype2.class})
public abstract class AbstractStereotype {
@XmlAttribute
public abstract String getPath();
}
Stereotype1
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Stereotype1 extends AbstractStereotype {
public String getPath() {
return "Path1";
}
}
Stereotype2
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Stereotype2 extends AbstractStereotype {
public String getPath() {
return "Path2";
}
}
Demo
import java.io.File;
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 jc = JAXBContext.newInstance(Stereotypes.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
Stereotypes stereotypes = (Stereotypes) unmarshaller.unmarshal(new File("input.xml"));
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(stereotypes, System.out);
}
}
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<stereotypes>
<stereotype1/>
<stereotype2/>
</stereotypes>
Output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<stereotypes>
<stereotype1 path="Path1"/>
<stereotype2 path="Path2"/>
</stereotypes>
For More Information
- http://bdoughan.blogspot.com/2010/11/jaxb-and-inheritance-using-substitution.html
JAXB inheritance - unmarshalling to subtypes declared in annotations?
You can use the @XmlSeeAlso
(javax.xml.bind.annotation) annotation for this.
@XmlDiscriminatorNode("@type")
@XmlSeeAlso({ CliActionDef.class, XsltActionDef.class, ... })
public static class ActionDef extends ContainerOfStackableDefs { ... }
UPDATE
The problem you are seeing in your latest updated is caused by the @XmlDescriminatorNode
not being placed on the root class in your inheritance hierarchy. When I removed ContainerOfStackableDefs
from the inheritance hierarchy using @XmlTransient
everything worked fine (see: http://blog.bdoughan.com/2011/06/ignoring-inheritance-with-xmltransient.html).
Demo
import java.io.File;
import java.util.*;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;
public class Demo {
@XmlTransient
public static class ContainerOfStackableDefs {
}
@XmlDiscriminatorNode("@type")
@XmlSeeAlso({ ManualActionDef.class, FileBasedActionDef.class, XsltActionDef.class })
public static class ActionDef extends ContainerOfStackableDefs {
//@XmlAttribute
//@XmlReadOnly
public String typeVal; // Was type, caused an exception.
//public List<PropertyBean> properties;
//@XmlAnyAttribute
public Map<String, String> attribs = new HashMap();
}
@XmlDiscriminatorValue("manual")
public static class ManualActionDef extends ActionDef {
}
@XmlDiscriminatorValue("fileBased")
public static class FileBasedActionDef extends ActionDef {
/** Path mask. Ant-like wildcards, EL. */
@XmlAttribute public String pathMask;
/** Where to store the result. May be a dir or a file. EL. */
@XmlAttribute public String dest;
}
@XmlDiscriminatorValue("xslt")
public static class XsltActionDef extends FileBasedActionDef {
/** XSLT template path. */
@XmlAttribute public String xslt;
}
@XmlRootElement
public static class Root {
@XmlElement(name="actionDef")
public List<ActionDef> actionDefs;
}
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum17453793/input.xml");
Root root = (Root) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<root>
<actionDef type="manual"/>
<actionDef pathMask="PATH MASK" dest="DEST" type="fileBased"/>
<actionDef xslt="XSLT" type="xslt"/>
</root>
JAXB inheritance, unmarshal to subclass of marshaled class
You're using JAXB 2.0 right? (since JDK6)
There is a class:
javax.xml.bind.annotation.adapters.XmlAdapter<ValueType,BoundType>
which one can subclass, and override following methods:
public abstract BoundType unmarshal(ValueType v) throws Exception;
public abstract ValueType marshal(BoundType v) throws Exception;
Example:
public class YourNiceAdapter
extends XmlAdapter<ReceiverPerson,Person>{
@Override public Person unmarshal(ReceiverPerson v){
return v;
}
@Override public ReceiverPerson marshal(Person v){
return new ReceiverPerson(v); // you must provide such c-tor
}
}
Usage is done by as following:
@Your_favorite_JAXB_Annotations_Go_Here
class SomeClass{
@XmlJavaTypeAdapter(YourNiceAdapter.class)
Person hello; // field to unmarshal
}
I'm pretty sure, by using this concept you can control the marshalling/unmarshalling process by yourself (including the choice the correct [sub|super]type to construct).
Related Topics
Specific Difference Between Bufferedreader and Filereader
Closing Bufferedreader and System.In
JSON Consumer of Polymorphic Objects
Create Java Runtime Image on One Platform for Another Using Jlink
Chat Client Emoticons Window Java
How to "Add" to Classpath Dynamically in Java
Problems Connecting via Https/Ssl Through Own Java Client
Can a Class Have No Constructor
Difference Between Openjdk and Adoptium/Adoptopenjdk
How to Achieve Conditional Resource Import in a Spring Xml Context
Arrays.Fill with Multidimensional Array in Java
How to Hide a Jframe in System Tray of Taskbar
Read Error Response Body in Java
Fastest Way to Iterate an Array in Java: Loop Variable VS Enhanced for Statement