我有一个根Xml document (name = "Entity")
,其中包含一个已知的Xml element (name = "Header")
和另一个名称未知的Xml元素,但已知它有一个内部XmlElement(name="label")
以下是可能的Xmls:
<Entity>
<Header>this is a header</Header>
<a>
<label>this is element A</label>
<otherElements/>
</a>
</Entity>
<Entity>
<Header>this is a different header</Header>
<b>
<label>this is some other element of name b</label>
<others/>
</b>
</Entity>
以下是我的JAXB注释类:
@XmlRootElement(name = "Entity")
@XmlAccessorType(XmlAccessType.NONE)
public class Entity {
@XmlElement(name = "Header")
private Header header;
@XmlElements( {
@XmlElement(name = "a", type=LabelledElement.A.class),
@XmlElement(name = "b", type=LabelledElement.B.class)
} )
private LabelledElement labelledElement;
// constructors, getters, setters...
}
@XmlAccessorType(XmlAccessType.NONE)
public abstract class LabelledElement {
@XmlElement
private String label;
@XmlAnyElement
private List<Element> otherElements;
public static class A extends LabelledElement {}
public static class B extends LabelledElement {}
}
这很棒!但后来我注意到它不仅是<a>
和<b>
可能是<c>
,<asd>
甚至<anything>
...
因此列出XmlElement(name = "xyz", type = LabelledElement.xyz.class)
显然不是正确的解决方案。
无论Entity#getLabelledElement()#getLabel()
名称是什么,我所关心的只是LabelledElement
。
这对JAXB来说是否可行?
答案 0 :(得分:1)
使用EclipseLink JAXB实现(MOXy),这应该有效:
@XmlRootElement(name = "Entity")
@XmlSeeAlso({LabelledElement.class}) //Might not be necessary
@XmlAccessorType(XmlAccessType.NONE)
public class Entity {
@XmlElement(name = "Header")
private Header header;
@XmlPath("child::*[position() = 2]")
@XmlJavaTypeAdapter(MapAdapter.class)
private Map<String,LabelledElement> labelledElementMap;
public LabelledElement getLabelledElement(){
return labelledElementMap.values().get(0);
}
// constructors, getters, setters...
}
MapAdapter
类:
public class MapAdapter extends XmlAdapter<MapAdapter.AdaptedMap, Map<String, LabelledElement>> {
public static class AdaptedMap {
@XmlVariableNode("key")
List<LabbeledElement> entries = new ArrayList<LabbeledElement>();
}
public static class AdaptedEntry {
@XmlTransient
public String key;
@XmlElement
public LabelledElement value;
}
@Override
public AdaptedMap marshal(Map<String, LabelledElement> map) throws Exception {
AdaptedMap adaptedMap = new AdaptedMap();
for(Entry<String, LabelledElement> entry : map.entrySet()) {
AdaptedEntry adaptedEntry = new AdaptedEntry();
adaptedEntry.key = entry.getKey();
adaptedEntry.value = entry.getValue();
adaptedMap.entries.add(adaptedEntry);
}
return adaptedMap;
}
@Override
public Map<String, LabelledElement> unmarshal(AdaptedMap adaptedMap) throws Exception {
List<AdaptedEntry> adaptedEntries = adaptedMap.entries;
Map<String, LabelledElement> map = new HashMap<String, LabelledElement>();
for(AdaptedEntry adaptedEntry : adaptedEntries) {
map.put(adaptedEntry.key, adaptedEntry.value);
}
return map;
}
}
作为参考,我的解决方案受到this link的启发。
答案 1 :(得分:0)
显然,EclipseLink JAXB(MOXy)实现可以实现,它允许您注释一个接口变量,提供在将XML绑定到Java时使用的Factory类和方法,请参阅此answer。
(已编辑以提供此方法的示例) 例如,您没有抽象类LabelledElement,而是具有接口LabelledElement :
public interface LabelledElement {
String getLabel();
}
然后让类A和B实现它,如下所示:
import javax.xml.bind.annotation.XmlElement;
public class A implements LabelledElement{
private String label;
@Override
@XmlElement(name="label")
public String getLabel() {
return label;
}
}
和实体类注释如下:
@XmlRootElement(name = "Entity")
@XmlAccessorType(XmlAccessType.NONE)
public class Entity {
@XmlElement(name = "Header")
private Header header;
@XmlRootElement
@XmlType(
factoryClass=Factory.class,
factoryMethod="createLabelledElement")
private LabelledElement labelledElement;
// constructors, getters, setters...
}
然后,正如我所链接的答案所示,您需要工厂类,如:
import java.lang.reflect.*;
import java.util.*;
public class Factory {
public A createA() {
return createInstance(A.class);
}
public B createB() {
return createInstance(B.class);;
}
private <T> T createInstance(Class<T> anInterface) {
return (T) Proxy.newProxyInstance(anInterface.getClassLoader(), new Class[] {anInterface}, new InterfaceInvocationHandler());
}
private static class InterfaceInvocationHandler implements InvocationHandler {
private Map<String, Object> values = new HashMap<String, Object>();
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if(methodName.startsWith("get")) {
return values.get(methodName.substring(3));
} else {
values.put(methodName.substring(3), args[0]);
return null;
}
}
}
}