2017-09-16 66 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>這是很多樣板代碼。