2017-09-16 97 views
0

对不起,很长的文章,谢谢! 解组嵌套使用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(代码如下)我有值,其中内部的一组被包裹的集的集成集装箱类。所以,我有一组值的容器,其中一个值是一个泛型类。问题:除非所选的泛型类是硬编码的,否则值不会被解析。


详细

注意的XML:

  • <Container>标签包含的<value>标签一个设置<value>标签可能包含任何内容。在JAXB中,集装箱类使用集<Ť>(在上面的例子中,我们有一个的ElementName类,仅具有一个“name”属性)。

  • 有只应该是正好两个<Container>标签(它的属性是两个项目的枚举,但在这个例子中,这只是一个字符串,使其更简单)。 附加问题(可选):是否有任何方法将标签的数量限制为两个(不多也不少)?

结束语所有这一切,我们有一个类,它具有Set<Container<ElementName>>。问题在于,最深的ElementName(应该表示<value>标记)类没有被JAXB解决(太多的间接级别?)。然而,JAXB将所有信息读入其内部类型ElementNSImpl(我通过在afterUnmarshall方法中放置一个断点来计算出它),它具有标签的属性值(即,name1,name2,,name4)。解组完成时没有错误,但是当我尝试访问任何<value>标签我得到这个:

java.lang.ClassCastException: com.sun.org.apache.xerces.internal.dom.ElementNSImpl cannot be cast to ElementName 

里面的集装箱类,我用

@XmlElement(name = "value") 

如果我指定的<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; 
    } 
} 

的ElementName

import javax.xml.bind.Unmarshaller; 
import javax.xml.bind.annotation.XmlAccessType; 
import javax.xml.bind.annotation.XmlAccessorType; 
import javax.xml.bind.annotation.XmlAttribute; 

/* 
* <* name = "name2"/> 
*/ 
@XmlAccessorType(XmlAccessType.FIELD) 
public class ElementName { 

    @XmlAttribute 
    private String name; 

    public String getName() { 

     return name; 
    } 
} 


主要

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()); 
} 

如果一切正常(如果我硬编码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] 


我试过的东西

Unmarshalling generic list with JAXB

  1. 主要思路是不是使用List<T>使用Wrapper<T>,将使用该名单内,并通过getter给它。我的容器已经是一种包装,除了它也有一个属性。但在的注解来区别:

    前:

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

    后:

    @XmlAnyElement(lax = true) 
    private Set<T> values; 
    
  2. 再次运行(JAXBContext.newInstance(Root.class, Container.class, ElementName.class)):没有什么变化,价值仍是键入ElementNSImpl。另外,我不喜欢我允许任何名称的标签,它必须是“价值”。

Intermittent ClassCastException from ElementNSImpl to own type during unmarshalling

在他们的情况下,问题是,他们创造JAXBContext不提供一些必要的类。不适用于我。

JAXB Unmarshalling XML Class Cast Exception

在他们的情况下,问题是Java的类型擦除,这意味着Java的编译时确定泛型类型,然后编译代码,然后将擦除泛型类型,所以JVM不知道那些泛型是什么?我敢打赌,这是我的情况,但我不知道如何检查或修复该问题。

回答

0

我找到了解决方案!我使用了由Blaise Doughan描述的方法here,这是XmlAdapter类的用法。我将一组值转换为一组字符串,将ElementName改为String。然后映射容器。

p.s.我保留ContainerOfElementName就像我的其他答案。这是重复的代码,但似乎没有更好的方法来创建JAXB层次结构。

p.p.s. Java Type Erasure出现问题(恕我直言)。编译后的Set<Container<ElementName>>只是Set。我注意到,JAXB停止解析泛型,当你触及第二级间接寻址时,就像T1<T2>很好,但是T1<T2<T3>>不是。

0

当前的解决办法 - 其他的答案是非常欢迎

我利用了一下用硬编码的泛型类型的想法 - 我分裂集装箱全班分成两个阶级,<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; 
    } 
} 

现在在类:

@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>这是很多样板代码。