繁体   English   中英

使用JAXB解组嵌套Set

[英]Unmarshalling a nested Set using JAXB

对不起,很长的帖子,在此先谢谢您!


我需要解组XML:

 <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方法内放置一个断点来弄清楚它),在其中它具有标签的属性值(即name1name2name3name4 )。 解组完成没有错误,但是当我尝试访问任何<value>标签时,我得到以下信息:

@XmlElement(name = "value", type = ElementName.class)

Container类中,我使用以下命令指定<value>的标记名称

 @XmlElement(name = "value") 

如果我只是在这里像这样硬编码类型:

 @XmlElement(name = "value", type = ElementName.class) 

然后,JAXB会正确解析标签。 但是,正如我说的那样,该类应该是通用的,因此对我而言并不是真正的解决方案。


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] 


我已经尝试过的事情

用JAXB解组通用列表

  1. 主要思想是使用Wrapper<T>而不是使用List<T> ,该方法将在内部使用该列表并通过getter给出。 我的容器除了已经具有属性之外,已经是包装器了。 但是Set的注释有所不同:

    之前:

     @XmlElement(name = "value") private Set<T> values; 

    后:

     @XmlAnyElement(lax = true) private Set<T> values; 
  2. 再次运行( JAXBContext.newInstance(Root.class, Container.class, ElementName.class) ): JAXBContext.newInstance(Root.class, Container.class, ElementName.class) ,值仍然是ElementNSImpl类型。 另外,我不喜欢允许标签使用任何名称,它必须是“值”。

在解组期间从ElementNSImpl到自己的类型的间歇性ClassCastException

在他们的情况下,问题在于他们创建了JAXBContext而没有提供某些所需的类。 不适用于我。

JAXB解组XML类强制转换异常

在他们的案例中,问题是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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM