I have a complex hierarchy of Java interfaces that I'd like to marshal (and not necessarily unmarshal) with JAXB. These interfaces represent objects that will be returned from a JAX-RS REST API as XML, JSON, YAML, etc. (I'm using RestEasy, which can marshal JAXB-annotated types in formats other than XML.)
The problem seems to be that JAXB is fundamentally class-oriented. I've done lots of web research on the difficulties with JAXB and interfaces, the closest solutions being MOXy JAXB - Map Interfaces to XML and JAXB and Interface Fronted Models. However, I have two main issues: a) I want to annotate/work in terms of interfaces, not the concrete classes (of which there will be multiple implementations and containing significant other state that should not be marshaled), and b) I have multiple levels of interface inheritance. Here's a sample of the interfaces, minus any JAXB annotations so far:
interface Uuided {
UUID getId();
}
interface Named {
String getName();
}
interface Component extends Uuided, Named {
Map<String, ComponentAttribute> getAttributes();
}
interface Attribute extends Named {
Type getType();
Object getValue();
}
interface ComponentAttribute extends Attribute {
Component getDeclaringComponent();
}
Ideally, this would generate something like:
<component id="xxx" name="thing">
<attributes>
<componentAttribute name="color">
<type><stringType/></type>
<value>green</value>
<declaringComponent idref="xxx"/>
</componentAttribute>
</attributes>
</component>
Clearly, in the abstract, this leads to issues like determining the most derived annotated interface, of which there could theoretically be more than one. In my case, however, I believe the concrete classes only implement a single interface that should be marshaled. Unmarshaling is not necessary, as I have separate classes defining upsert properties.
So my question is, is this even possible to handle with JAXB, and if so, how? Even if I have to be very explicit in defining bindings, adapters, etc., I'd like to work within the JAXB framework to get the benefit of all the non-XML providers in RestEasy.
Short answer: use @XmlElement(type = Object.class)
on your interface field.
Details below:
I have found 2 ways in which you could make JAXB serialize your interfaces:
@XmlAnyElement
@XmlElement(type = Object.class)
1.@XmlAnyElement
Simply annotate your interface type field with @XmlAnyElement
and JAXB will serialize the interface from it's concrete type. Don't forget to annotate the concrete types with @XmlRootElement
and to add the concrete types to the JAXBContext. Full example follows:
public class InterfaceSerializer {
@XmlRootElement
public static class Pojo {
Pojo() {
field1 = new PojoFieldImpl1();
field2 = new PojoFieldImpl2();
field3 = new PojoFieldImpl1();
}
@XmlAnyElement
public IPojoField field1;
@XmlAnyElement
public IPojoField field2;
@XmlAnyElement
public IPojoField field3;
@Override
public String toString() {
return "field1 = " + field1 + "\nfield2 = " + field2 + "\nfield3 = " + field3;
}
}
public static interface IPojoField {
}
@XmlRootElement
public static class PojoFieldImpl1 implements IPojoField {
PojoFieldImpl1() {
value = "PojoFieldImpl1 value";
}
public String value;
@Override
public String toString() {
return value;
}
}
@XmlRootElement
public static class PojoFieldImpl2 implements IPojoField {
PojoFieldImpl2() {
value = "PojoFieldImpl2 value1";
value2 = "PojoFieldImpl2 value2";
}
public String value;
public String value2;
@Override
public String toString() {
return value + " " + value2;
}
}
public static void main(String []args) throws JAXBException {
Pojo pojo = new Pojo();
JAXBContext jaxbContext = JAXBContext.newInstance(Pojo.class, PojoFieldImpl1.class, PojoFieldImpl2.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.marshal(pojo, new File("interfaceSerializer.xml"));
}
}
Output XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<pojo>
<pojoFieldImpl1>
<value>PojoFieldImpl1 value</value>
</pojoFieldImpl1>
<pojoFieldImpl2>
<value>PojoFieldImpl2 value1</value>
<value2>PojoFieldImpl2 value2</value2>
</pojoFieldImpl2>
<pojoFieldImpl1>
<value>PojoFieldImpl1 value</value>
</pojoFieldImpl1>
</pojo>
The downsides of this method:
These downsides are fixed in the second solution:
2.@XmlElement(type = Object.class)
I've stumbled upon this in mikesir87's blog post. Simply replace the @XmlAnyElement
annotations from above with @XmlElement(type = Object.class)
You should have something like this in the Pojo class from above:
@XmlElement(type = Object.class)
public IPojoField field1;
@XmlElement(type = Object.class)
public IPojoField field2;
@XmlElement(type = Object.class)
public IPojoField field3;
Re-running our example, the resulting XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<pojo>
<field1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="pojoFieldImpl1">
<value>PojoFieldImpl1 value</value>
</field1>
<field2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="pojoFieldImpl2">
<value>PojoFieldImpl2 value1</value>
<value2>PojoFieldImpl2 value2</value2>
</field2>
<field3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="pojoFieldImpl1">
<value>PojoFieldImpl1 value</value>
</field3>
</pojo>
This can also be deserialized:
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Pojo unmarshalledPojo = (Pojo) unmarshaller.unmarshal(new File("interfaceSerializer.xml"));
System.out.println(unmarshalledPojo);
Resulting output:
field1 = PojoFieldImpl1 value
field2 = PojoFieldImpl2 value1 PojoFieldImpl2 value2
field3 = PojoFieldImpl1 value
Probably a "hackish" solution, but it gets the job done.
I think the answer is that JAXB isn't at all intended to support this and that it's foolish to try to force it. Also, JAXB-driven JSON marshaling turns out not to be ideal either.
I ended up writing my own marshaling framework, with its own set of annotations:
@MarshalMixin // marshal fields but not a top-level object
interface Uuided {
@MarshalAsString // ignore properties; just call toString()
@MarshalId // treat as identifier for @MarshalUsingIds or cyclic ref
UUID getId();
}
@MarshalMixin
interface Named {
@MarshalId
String getName();
}
@MarshalObject // top-level marshaled object providing class name
interface Component extends Uuided, Named {
@MarshalAsKeyedObjectMap(key = "name") // see description below
Map<String, ComponentAttribute> getAttributes();
}
@MarshalObject
interface Attribute extends Named {
Type getType();
@MarshalDynamic // use run-time (not declared) type
Object getValue();
}
interface ComponentAttribute extends Attribute {
@MarshalUsingIds
Component getDeclaringComponent();
}
The generated marshalers write to an abstraction layer (currently implemented for JSON and XML). This gives a lot more flexibility to make the output natural for different representations without a ton of annotations and adapters. E.g., what I'm calling keyed-object maps, where each object contains its map key. In JSON, you want a map, but in XML, you want a sequence:
{..., map: {'a': {'name': 'a', ...}, 'b': {'name: 'b', ...}, ...}, ...}
...<map><thing name='a'>...</thing><thing name='b'>...</thing></map>...
Seems like as many as 4 other people care about this too, so hopefully I can open-source it eventually. :-)
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