Reputation: 75
I've been reading all the questions on this subject, but none of them relate to my problem. I have these classes (each of them in their own file):
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "Root")
public class Root {
@XmlElements({
@XmlElement(name="Child", type=ChildImpl.class),
@XmlElement(name="Child", type=ChildImpl2.class)
})
protected List<AbstractChild> children;
public List<AbstractChild> getChildren() {
if (children == null) {
children = new ArrayList<AbstractChild>();
}
return this.children;
}
}
@XmlTransient
public abstract class AbstractChild {
@XmlElement(name = "Other", required = true)
protected List<Other> others; // class Other is not important for my question, so I'll skip its implementation details
public List<Other> getOthers() {
if (others == null) {
others = new ArrayList<Other>();
}
return this.others;
}
}
@XmlRootElement(name = "Child")
public class ChildImpl extends AbstractChild {
// custom behavior
}
@XmlRootElement(name = "Child")
public class ChildImpl2 extends AbstractChild {
// different custom behavior
}
And then, I have the class that performs the unmarshalling:
JAXBContext jaxbContext = JAXBContext.newInstance(Root.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
result = (Root) jaxbUnmarshaller.unmarshal(new ByteArrayInputStream(fileContent)); // where fileContent is an instance of byte[]
Now, depending on our context, I want the unmarshaller to use a specific implementation of Child for the Root object... but that's where I'm struggling: I have no idea how to signal the unmarshaller to use a specific subclass for Child (in our files, the structure of Root, Child and Other is always the same. However, how we process Child depends on the source folder for each file). I've tried passing the concrete class when creating the context -just for testing purposes- (e.g. JAXBContext.newInstance(Root.class, ChildImpl.class)), but for some reason, the unmarshaller always resolve to the class entered last in the @XmlElements array (In this case ChildImpl2).
I've also tried removing the @XmlElements annotation, but the unmarshaller doesn't know to process the Version elements since the parent class is abstract (that's also the reason why I added the @XmlTransient annotation; I have no interest in trying to instance AbstractChild)
Any ideas? Thanks in advance!
Upvotes: 0
Views: 1240
Reputation: 81
If the structure in the xml-files is always the same, having an abstract class AbstractChild makes no sense (in my opinion). The logic should be elsewhere in your code, in a strategy or something like that.
But it is kind of possible:
Have an XmlElement representing the child.
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "child")
public class Child {
@XmlElement(name = "name")
private String name;
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
}
Have another Class like MyAbstractChild
public abstract class MyAbstractChild {
private final Child child;
public AbstractChild(final Child child) {
this.child = child;
}
public Child getChild() {
return child;
}
}
And subclasses for each behavior you want:
public class MyChildImpl extends MyAbstractChild {
public MyChildImpl(final Child child) {
super(child);
}
// custom behavior here
}
Then you can implement an XmlAdapter:
public class MyChildImpAdapter extends XmlAdapter<Child, MyAbstractChild> {
private final Class<? extends AbstractChild> implClass;
public MyChildImpAdapter(final Class<? extends MyAbstractChild> implClass){
this.implClass = implClass;
}
@Override
public MyAbstractChild unmarshal(final Child child) throws Exception {
if (MyChildImpl.class.equals(this.implClass)) {
return new MyChildImpl(child);
} else {
return new MyChildImpl2(child);
}
}
@Override
public Child marshal(final MyAbstractChild abstractChild) throws Exception {
return abstractChild.getChild();
}
}
Now you can use the new type MyAbstractChild in your root element:
@XmlJavaTypeAdapter(value = MyChildImpAdapter.class)
protected List<MyAbstractChild> children;
Finally you can train the unmarshaller to use your adapter of choice:
final JAXBContext jaxbContext = JAXBContext.newInstance(Root.class);
final Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
jaxbUnmarshaller.setAdapter(MyChildImpAdapter.class, new MyChildImpAdapter(MyChildImpl.class));
final InputStream inputStream = getClass().getResourceAsStream("some.xml");
final Root root = (Root) jaxbUnmarshaller.unmarshal(inputStream);
Now you have your Root-Element with MyChildImpl-Objects in the children.
But as I wrote in the beginning: there should be another option :)
Upvotes: 1