my current code marshalls perfectly, and I get the element I want inside of my resulting XML. i.e. <food>Beef</food>
However, the problem comes when I have to unmarshall this back to a java object. Everything returns fine except the food variable. I originally did not have the XmlElement(required = true)
on top, and the food element would always unmarshal back to null. Then, I added the required=true section and I am getting issues with the interface. I did some digging and from what I can gather, jaxb can't really unmarshal into an interface since it doesn't know the concrete type to marshall into.
Current error if this helps:
Can not set FoodInterface field BigPayload.food to
com.sun.org.apache.xerces.internal.dom.ElementNSImpl
My Java classes are as follows:
@XmlSeeAlso({MeatFoods.class, VeggieFoods.class})
@XmlType(name ="BigPayload", propOrder = //stuff goes here
@XmlRootElement(name = foodPayload)
public class BigPayload implements Payload{
@XmlElements({@XmlElement(type = MeatFoods.class),
@XmlElement(type = VeggieFoods.class),
@XmlElement(required = true)})
protected FoodInterface food;
protected Grade grade;
//grade/food setters and getters
}
@XmlTransient //If this isn't here, I get the jaxB cannot handle interfaces and no default constructor error
public interface FoodInterface{ //stuff here}
@XmlType(name = "MeatFoods")
@XmlEnum
public enum MeatFoods implements FoodInterface{
Chicken(1, Chicken)
Beef(2, Beef)
Pork(3, Pork)
int value;
String name;
@Override
public int getValue()
@Override
public String getName()
public static FoodInterface getEnumFromValue(int value){//gets stuff}
public static FoodInterface getEnumFromName(String name){//gets stuff}
}
I just wanted to know if that is correct, and there's no real good way to unmarshall an interface type. Is this true? I saw a lot of other questions were about marshalling interfaces, and the unmarshalling questions did not really get answers to my satisfaction. Any answer is appreciated, and I know this isn't a minimal reproducible example, but I'm more looking for a verbal answer instead of a code fix or anything. Although, if there's anything blatantly wrong in the code please let me know!
For the standard cases JAXB can only use (abstract) classes not interfaces.
Options that i can think of
@XmlAdapter
. See example: [1]Object
for JAXB Bindings and expose the interface with casting. (Maybe add validation logic into the `afterUnmarshal(Unmarshaller u, Object parent). [2]@XmlAnyElement
and do some further processing in afterUnmarshal(Unmarshaller, Object)
, add @XmlTransient
to the target. See example: [3]With some creativity there might be some other options. But i think all boil down to bascially: try to get to the "raw" parsing options and fill the interface reference manually.
[1]
public static interface Food {
String name();
}
public enum Veggie implements Food {
SALAD;
}
public static enum Meat implements Food {
CHICKEN;
}
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement
public static class UseInterface {
@XmlJavaTypeAdapter(FoodAdapter.class)
@XmlAttribute
private Food food;
public Food getFood() {
return food;
}
public void setFood(Food food) {
this.food = food;
}
}
public static class FoodAdapter extends XmlAdapter<String, Food> {
@Override
public Food unmarshal(String v) throws Exception {
try {
return Veggie.valueOf(v);
} catch (IllegalArgumentException e) {
}
try {
return Meat.valueOf(v);
} catch (IllegalArgumentException e) {
}
throw new IllegalArgumentException("Unknown Food:" + v);
}
@Override
public String marshal(Food v) throws Exception {
return v.name();
}
}
[2]
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement
public static class UseInterface {
@XmlElement
private Object food;
public Food getFood() {
return (Food) food;
}
public void setFood(Food food) {
this.food = food;
}
public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
if (food != null && !(food instanceof Food)) {
throw new IllegalArgumentException("food is of wrong type: " + food.getClass().getName());
}
}
}
JAXBContext newInstance = JAXBContext.newInstance(UseInterface.class, Meat.class, Veggie.class);
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><useInterface><food xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"meat\">CHICKEN</food></useInterface>";
newInstance.createUnmarshaller().unmarshal(new StringReader(xml));
[3]
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement
public static class UseInterface {
@XmlAnyElement
private org.w3c.dom.Element foo;
@XmlTransient
private SomeInterface ifc
public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
NamedNodeMap attributes = foo.getAttributes();
// do something with foo on DOM level to bind the subtree to an interface manually
}
}