[英]Unmarshalling a nested Set using JAXB
对不起,很长的帖子,在此先谢谢您!
<RootTag> <Container type="type1"> <value name="name1"/> <value name="name2"/> </Container> <Container type="type2"> <value name="name3"/> <value name="name4"/> </Container> </RootTag>
在我的JAXB中(下面的代码),我有一组值集合,其中内部集合包装在Container类中。 因此,我有一组值的容器,其中值是通用类。 问题:除非对所选的通用类进行了硬编码,否则无法解析值。
关于XML的注释:
<Container>
标记包含一组的<value>
标记。 <value>
标签可能包含任何内容。 在JAXB中, Container类使用Set <T> (在上面的示例中,我们有一个ElementName类,它仅具有一个“名称”属性)。
仅应该有两个<Container>
标记(它的属性是两个项目的枚举 ,但在此示例中,它只是一个String ,使它更简单)。 附加问题 (可选):是否可以将标签数量限制为恰好两个(不多不少)?
包装所有这些,我们有一个Root类,它具有Set<Container<ElementName>>
。 问题是JAXB无法解析最深的ElementName
(应该表示<value>
标签)类(间接层太多?)。 但是,JAXB会将所有信息读入其内部类型ElementNSImpl
(我通过在afterUnmarshall
方法内放置一个断点来弄清楚它),在其中它具有标签的属性值(即name1
, name2
, name3
和name4
)。 解组完成没有错误,但是当我尝试访问任何<value>
标签时,我得到以下信息:
@XmlElement(name = "value", type = ElementName.class)
在Container类中,我使用以下命令指定<value>
的标记名称
@XmlElement(name = "value")
如果我只是在这里像这样硬编码类型:
@XmlElement(name = "value", type = ElementName.class)
然后,JAXB会正确解析标签。 但是,正如我说的那样,该类应该是通用的,因此对我而言并不是真正的解决方案。
根
import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.*; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /* * <RootTag> * * * </RootTag> */ @XmlRootElement(name = "RootTag") public class Root { /* <Container type = "type1"> * <value name = "name1"/> * <value name = "name2"/> * </Container> * <Container type = "type2"> * <value name = "name3"/> * <value name = "name4"/> * </Container> */ @XmlElement(name = "Container") private Set<Container<ElementName>> containers; // for each of the two Container types // we create a set of its corresponding values' names // and then we null the containers field @XmlTransient private Map<String, Set<String>> valuesNames = new HashMap<>(2); //Basically that is @PostConstruct, but works with JAXB void afterUnmarshal(Unmarshaller u, Object parent) { containers.forEach(container -> valuesNames.put(container.getType(), container.getValues().stream() .map(ElementName::getName) .collect(Collectors.toSet()))); containers = null; } /** return unique value names from both container types */ public Set<String> getAllValuesNames() { return valuesNames.values().stream() .flatMap(Set::stream) .collect(Collectors.toSet()); } }
容器
import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.*; import java.util.Set; /* * <* type = "type1"> * <value *>* * <value *>* * </*> */ @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Container<T> { @XmlAttribute private String type; @XmlElement(name = "value") private Set<T> values; public String getType() { return type; } public Set<T> getValues() { return values; } }
元素名称
public static void main(String[] args) throws JAXBException {
final String xmlSerialized = "<RootTag>\n" +
" \n" +
" <Container type=\"type1\">\n" +
" <value name=\"name1\"/>\n" +
" <value name=\"name2\"/>\n" +
" </Container>\n" +
" \n" +
" <Container type=\"type2\">\n" +
" <value name=\"name3\"/>\n" +
" <value name=\"name4\"/>\n" +
" </Container>\n" +
" \n" +
"</RootTag>";
System.out.println("XML:\n" +
"=======================================\n" +
xmlSerialized +
"\n=======================================");
//works just the same with only Root.class
JAXBContext context = JAXBContext.newInstance(Root.class, Container.class, ElementName.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
Root xmlDeserialized = (Root) unmarshaller.unmarshal(new StringReader(xmlSerialized));
System.out.println("Values' names: " + xmlDeserialized.getAllMetricNames());
}
public static void main(String[] args) throws JAXBException { final String xmlSerialized = "<RootTag>\\n" + " \\n" + " <Container type=\\"type1\\">\\n" + " <value name=\\"name1\\"/>\\n" + " <value name=\\"name2\\"/>\\n" + " </Container>\\n" + " \\n" + " <Container type=\\"type2\\">\\n" + " <value name=\\"name3\\"/>\\n" + " <value name=\\"name4\\"/>\\n" + " </Container>\\n" + " \\n" + "</RootTag>"; System.out.println("XML:\\n" + "=======================================\\n" + xmlSerialized + "\\n======================================="); //works just the same with only Root.class JAXBContext context = JAXBContext.newInstance(Root.class, Container.class, ElementName.class); Unmarshaller unmarshaller = context.createUnmarshaller(); Root xmlDeserialized = (Root) unmarshaller.unmarshal(new StringReader(xmlSerialized)); System.out.println("Values' names: " + xmlDeserialized.getAllMetricNames()); }
如果一切正常(如果我在Container类内的注释中对ElementName.class进行了硬编码),它将给出以下输出:
XML: ======================================= <RootTag> <Body> <Container type="type1"> <value name="name1"/> <value name="name2"/> </Container> <Container type="type2"> <value name="name3"/> <value name="name4"/> </Container> </Body> </RootTag> ======================================= Values' names: [name4, name3, name2, name1]
主要思想是使用Wrapper<T>
而不是使用List<T>
,该方法将在内部使用该列表并通过getter给出。 我的容器除了已经具有属性之外,已经是包装器了。 但是Set的注释有所不同:
之前:
@XmlElement(name = "value") private Set<T> values;
后:
@XmlAnyElement(lax = true) private Set<T> values;
再次运行( JAXBContext.newInstance(Root.class, Container.class, ElementName.class)
): JAXBContext.newInstance(Root.class, Container.class, ElementName.class)
,值仍然是ElementNSImpl
类型。 另外,我不喜欢允许标签使用任何名称,它必须是“值”。
在解组期间从ElementNSImpl到自己的类型的间歇性ClassCastException
在他们的情况下,问题在于他们创建了JAXBContext
而没有提供某些所需的类。 不适用于我。
在他们的案例中,问题是Java类型擦除,这意味着Java在编译时确定泛型类型,然后编译代码,然后从泛型中擦除类型 ,因此JVM不知道这些泛型类型是什么。 我敢打赌这是我的情况,但是我不知道如何检查或解决该问题。
当前解决方法-非常欢迎其他答案
我对硬编码泛型类型使用了一些想法-我将Container类分为两个类,而<value>
类现在是内部的(但我将它们公开,以便它们可以相互继承):
容器
import javax.xml.bind.annotation.*;
import java.util.Set;
/*
* <Container type = "type1">
* *
* </Container>
*/
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public abstract class Container {
@XmlAttribute
private String type;
public String getType() {
return type;
}
/** Derived classes will declare the generic type for us. */
public abstract Set<?> getValues();
}
ContainerOfElementName
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import java.util.Set;
import java.util.stream.Collectors;
/*
* <Container type = "type1">
* <value name="name1"/>
* <value name="name2"/>
* </Container>
*/
public class ContainerOfElementName extends Container {
@XmlElement(name = "value")
private Set<ElementName> values;
@Override
public Set<ElementName> getValues() {
return values;
}
public Set<String> getNames() {
return values.stream().map(v -> v.name).collect(Collectors.toSet());
}
/*
* <* name = "name2"/>
*/
@XmlAccessorType(XmlAccessType.FIELD)
public static class ElementName {
@XmlAttribute
private String name;
}
}
现在在Root类中:
@XmlElement(name = "Container")
private Set<ContainerOfElementName> containers;
void afterUnmarshal(Unmarshaller u, Object parent) {
containers.forEach(container -> valuesNames.put(container.getType(),
container.getNames()));
containers = null;
}
那行得通,但是它迫使我为表示<value>
每个可能的类创建Container的子类,这是很多样板代码。
我找到了解决方案! 我使用了Blaise Doughan 此处介绍的方法,即XmlAdapter
类的用法。 我将一组值转换为String组,使ElementName
适应String
。 然后将容器映射。
ps我像其他答案一样保留了ContainerOfElementName
。 那是重复的代码,但是似乎没有更好的方法来创建JAXB层次结构。
pps问题是Java类型清除(IMHO)。 编译后的Set<Container<ElementName>>
只是Set
。 我注意到,当您到达间接访问的第二层时,JAXB停止解析泛型,例如T1<T2>
可以,但是T1<T2<T3>>
则不能。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.