2009-05-03 14 views
31

Mam kilka złożonych struktur danych jakJAXB: Jak powinienem marnować złożone zagnieżdżone struktury danych?

Map< A, Set<B> > 
Set< Map< A, B > > 
Set< Map< A, Set<B> > > 
Map< A, Map< B, Set<C> > > 
and so on (more complex data structures) 

Uwaga: W moim przypadku nie ma znaczenia, czy mogę użyć Set lub List.

Teraz wiem, że JAXB pozwól mi określić XmlAdapter „s, to w porządku, ale nie chcę, aby zdefiniować XmlAdapter dla każdego z podanych struktur danych (to byłoby po prostu zbyt dużo kopiowaniem i wklej kod).

starałem się osiągnąć mój cel, ogłaszając dwa XmlAdapters uogólniając:

  • jeden do mapy: MapAdapter<K,V>
  • jeden dla Zestaw: SetAdapter<V>

problem:
JAXB narzeka jak następuje:

javax.xml.bind.JAXBException: 
class java.util.Collections$UnmodifiableMap nor any of its 
    super class is known to this context. 

Oto moja klasa adaptera:

import java.util.*; 
import javax.xml.bind.annotation.*; 
import javax.xml.bind.annotation.adapters.*; 

public class Adapters { 

public final static class MapAdapter<K, V> 
     extends XmlAdapter<MapAdapter.Adapter<K, V>, Map<K, V>> { 

    @XmlType 
    @XmlRootElement 
    public final static class Adapter<K, V> { 

     @XmlElement 
     protected List<MyEntry<K, V>> key = new LinkedList<MyEntry<K, V>>(); 

     private Adapter() { 
     } 

     public Adapter(Map<K, V> original) { 
      for (Map.Entry<K, V> entry : original.entrySet()) { 
       key.add(new MyEntry<K, V>(entry)); 
      } 
     } 

    } 

    @XmlType 
    @XmlRootElement 
    public final static class MyEntry<K, V> { 

     @XmlElement 
     protected K key; 

     @XmlElement 
     protected V value; 

     private MyEntry() { 
     } 

     public MyEntry(Map.Entry<K, V> original) { 
      key = original.getKey(); 
      value = original.getValue(); 
     } 

    } 

    @Override 
    public Adapter<K, V> marshal(Map<K, V> obj) { 
     return new Adapter<K, V>(obj); 
    } 

    @Override 
    public Map<K, V> unmarshal(Adapter<K, V> obj) { 
     throw new UnsupportedOperationException("unmarshalling is never performed"); 
    } 

} 

} 

Oto mój przypadek testy JUnit:

import java.io.*; 
import java.util.*; 
import javax.xml.bind.*; 
import javax.xml.bind.annotation.*; 
import javax.xml.bind.annotation.adapters.*; 
import org.junit.*; 
import static java.lang.System.*; 

public class SomeTest { 

@Test 
public void _map2() 
     throws Exception { 

    Map<String, Map<String, String>> dataStructure = 
      new HashMap<String, Map<String, String>>(); 

    Map<String, String> inner1 = new HashMap<String, String>(); 
    Map<String, String> inner2 = new HashMap<String, String>(); 

    dataStructure.put("a", inner1); 
    dataStructure.put("b", inner1); 

    inner1.put("a1", "1"); 
    inner1.put("a2", "2"); 
    inner2.put("b1", "1"); 
    inner2.put("b2", "2"); 

    JAXBContext context = JAXBContext.newInstance(Adapters.XMap.class, 
      Adapters.XCount.class, Adapters.XEntry.class); 

    Marshaller marshaller = context.createMarshaller(); 
    marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true); 
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 

    marshaller.setAdapter(new Adapters.MapAdapter()); 

    StringWriter sw = new StringWriter(); 

    marshaller.marshal(dataStructure, sw); 
    out.println(sw.toString()); 
} 

} 

Odpowiedz

20

mam rozwiązany problem bez XmlAdapter na.

Pisałem JAXB-adnotacjami obiektów dla mapą, Map.Entry i Collection.
Główną ideą jest wewnątrz metoda xmlizeNestedStructure (...):

Spójrz na kod:

public final class Adapters { 

private Adapters() { 
} 

public static Class<?>[] getXmlClasses() { 
    return new Class<?>[]{ 
       XMap.class, XEntry.class, XCollection.class, XCount.class 
      }; 
} 

public static Object xmlizeNestedStructure(Object input) { 
    if (input instanceof Map<?, ?>) { 
     return xmlizeNestedMap((Map<?, ?>) input); 
    } 
    if (input instanceof Collection<?>) { 
     return xmlizeNestedCollection((Collection<?>) input); 
    } 

    return input; // non-special object, return as is 
} 

public static XMap<?, ?> xmlizeNestedMap(Map<?, ?> input) { 
    XMap<Object, Object> ret = new XMap<Object, Object>(); 

    for (Map.Entry<?, ?> e : input.entrySet()) { 
     ret.add(xmlizeNestedStructure(e.getKey()), 
       xmlizeNestedStructure(e.getValue())); 
    } 

    return ret; 
} 

public static XCollection<?> xmlizeNestedCollection(Collection<?> input) { 
    XCollection<Object> ret = new XCollection<Object>(); 

    for (Object entry : input) { 
     ret.add(xmlizeNestedStructure(entry)); 
    } 

    return ret; 
} 

@XmlType 
@XmlRootElement 
public final static class XMap<K, V> { 

    @XmlElementWrapper(name = "map") 
    @XmlElement(name = "entry") 
    private List<XEntry<K, V>> list = new LinkedList<XEntry<K, V>>(); 

    public XMap() { 
    } 

    public void add(K key, V value) { 
     list.add(new XEntry<K, V>(key, value)); 
    } 

} 

@XmlType 
@XmlRootElement 
public final static class XEntry<K, V> { 

    @XmlElement 
    private K key; 

    @XmlElement 
    private V value; 

    private XEntry() { 
    } 

    public XEntry(K key, V value) { 
     this.key = key; 
     this.value = value; 
    } 

} 

@XmlType 
@XmlRootElement 
public final static class XCollection<V> { 

    @XmlElementWrapper(name = "list") 
    @XmlElement(name = "entry") 
    private List<V> list = new LinkedList<V>(); 

    public XCollection() { 
    } 

    public void add(V obj) { 
     list.add(obj); 
    } 

} 

} 

to działa!

Spójrzmy na wyjście demo:

<xMap> 
    <map> 
     <entry> 
      <key xsi:type="xCount" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
       <count>1</count> 
       <content xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">a</content> 
      </key> 
      <value xsi:type="xCollection" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
       <list> 
        <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">a1</entry> 
        <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">a2</entry> 
        <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">a3</entry> 
       </list> 
      </value> 
     </entry> 
     <entry> 
      <key xsi:type="xCount" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
       <count>2</count> 
       <content xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">b</content> 
      </key> 
      <value xsi:type="xCollection" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
       <list> 
        <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">b1</entry> 
        <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">b3</entry> 
        <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">b2</entry> 
       </list> 
      </value> 
     </entry> 
     <entry> 
      <key xsi:type="xCount" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
       <count>3</count> 
       <content xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">c</content> 
      </key> 
      <value xsi:type="xCollection" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
       <list> 
        <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">c1</entry> 
        <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">c2</entry> 
        <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">c3</entry> 
       </list> 
      </value> 
     </entry> 
    </map> 
</xMap> 

Niestety, wyjście demo korzysta także strukturę danych zwaną „liczyć” która nie jest wymieniona w kodzie źródłowym adaptera.

BTW: czy ktoś wie jak usunąć wszystkie te irytujące i (w moim przypadku) niepotrzebnego xsi: typ atrybuty?

+0

Super! Być może kiedyś będę musiał to zrobić - czy możesz również opublikować podsumowanie tego, co musisz zrobić w pliku powiązań .xjb? –

+0

Nigdy nie użyłem pliku powiązań .xjb;) Proponowane rozwiązanie po prostu działa;) –

+1

Musiałem dodać '@XmlSeeAlso ({Adapters.XMap.class, Adapters.XCollection.class, Adapters.XEntry.class}) 'na mojej klasie adnotacji JAXB, aby to zadziałało. – ajitomatix

1

Wygląda na to, że jesteś na dobrej drodze z XMLAdapter ... komunikat o błędzie może być wskazówką:

klasa java.util.Collections $ UnmodifiableMap ani żadna z jego super klasy nie jest znana w tym kontekście z .

czy owiniesz mapę za pomocą Collections.unmodifiableMap() w dowolnym miejscu? Gdzie dokładnie występuje błąd?


(poprzednia odpowiedź lewo jak nieświeży rekord ciekawy)

Można tworzyć niestandardowe naziemnego/unmarshaller logika, która działa trochę więcej straighforward niż idei Adapters (myślę, I haven” t użył tego wcześniej).

Zasadniczo chodzi o to, że określasz funkcję statyczną do wykonania pracy, możesz również utworzyć niestandardową klasę. (Zazwyczaj umieszczam funkcję statyczną w klasie, o której mowa, ale nie musisz.) Następnie umieszczasz wiersz w pliku .XJB, aby powiedzieć JAXB, aby użył twojej statycznej funkcji.

Teraz, gdy przyjrzałem się istniejącemu kodowi, widzę, że wszystko, co robiłem, to przekształcanie ciągu atrybutów w niestandardowy obiekt Java. Oto kod, dla odniesienia, ale to tylko dla atrybutów.

plik JAXB:

<?xml version="1.0" ?> 
<jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    jaxb:version="2.0"> 
    <jaxb:bindings schemaLocation={your schema} node="/xsd:schema"> 
     <jaxb:bindings node={some XPATH expression to select a node}> 
      <jaxb:bindings node={maybe another XPATH relative to the above}> 
       <jaxb:property> 
        <jaxb:baseType> 
         <jaxb:javaType name={your custom Java class} 
          parseMethod={your static method for unmarshaling} 
          printMethod={your static method for marshaling} 
          /> 
        </jaxb:baseType> 
       </jaxb:property> 
      </jaxb:bindings> 
     </jaxb:bindings> 
    </jaxb:bindings> 
</jaxb:bindings> 

(parseMethod i printMethod konwersji do/z ciągów atrybutów)

+0

Dziękuję za odpowiedź! Nie, nie pakuję nigdzie swoich map, to dziwne. Może się zdarzyć gdzieś wewnętrznie w JAXB. –

+0

Czy próbowałeś debugowania, np. umieszczanie punktów przerwania na twoim kodzie marshall/unmarshaling? –

+0

Skończyło się na pisaniu własnych "wrapperów", bez XmlAdapter, zobacz moją odpowiedź. –

1

Oto mój marshaller/unmarshaller dla listy klasy @XmlType.

Np

//Type to marshall 
@XmlType(name = "TimecardForm", propOrder = { 
"trackId", 
"formId" 
}) 
public class TimecardForm { 

    protected long trackId; 
    protected long formId; 
    ... 
} 

//a list holder 
@XmlRootElement 
public class ListHodler<T> { 
    @XmlElement 
    private List<T> value ; 

    public ListHodler() { 
    } 

    public ListHodler(List<T> value) { 
     this.value = value; 
    } 

    public List<T> getValue() { 
     if(value == null) 
      value = new ArrayList<T>(); 
     return this.value; 
    } 
} 

//marshall collection of T 
public static <T> void marshallXmlTypeCollection(List<T> value, 
     Class<T> clzz, OutputStream os) { 
    try { 
     ListHodler<T> holder = new ListHodler<T>(value); 
     JAXBContext context = JAXBContext.newInstance(clzz, 
       ListHodler.class); 
     Marshaller m = context.createMarshaller(); 
     m.setProperty("jaxb.formatted.output", true); 

     m.marshal(holder, os); 
    } catch (JAXBException e) { 
     e.printStackTrace(); 
    } 
} 

//unmarshall collection of T 
@SuppressWarnings("unchecked") 
public static <T> List<T> unmarshallXmlTypeCollection(Class<T> clzz, 
     InputStream input) { 
    try { 
     JAXBContext context = JAXBContext.newInstance(ListHodler.class, clzz); 
     Unmarshaller u = context.createUnmarshaller(); 

     ListHodler<T> holder = (ListHodler<T>) u.unmarshal(new StreamSource(input)); 

     return holder.getValue(); 
    } catch (JAXBException e) { 
     e.printStackTrace(); 
    } 

    return null; 
} 
2

Poniżej znajduje się kod z „dexmlize” zdolności w oparciu o powyższy kod Iwana. Zastosowanie:

Map<String, List> nameMapResult = (Map<String, List>) Adapters.dexmlizeNestedStructure(unmarshallResult); 

W celu przywrócenia i klasę kolekcję map, nowe pole będzie xmlized do rejestrowania informacji o klasie. Szczegółowy kod:

class Adapters { 
    private Adapters() { 
    } 
    public static Class<?>[] getXmlClasses() { 
      return new Class<?>[]{XMap.class, XEntry.class, XCollection.class}; 
    } 
    public static Object xmlizeNestedStructure(Object input) { 
      if (input instanceof Map<?, ?>) { 
        return xmlizeNestedMap((Map<?, ?>) input); 
      } 
      if (input instanceof Collection<?>) { 
        return xmlizeNestedCollection((Collection<?>) input); 
      } 
      return input; // non-special object, return as is 
    } 

    public static Object dexmlizeNestedStructure(Object input) { 
     if (input instanceof XMap<?, ?>) { 
       return dexmlizeNestedMap((XMap<?, ?>) input); 
     } 
     if (input instanceof XCollection<?>) { 
       return dexmlizeNestedCollection((XCollection<?>) input); 
     } 
     return input; // non-special object, return as is 
    } 

    private static Object dexmlizeNestedCollection(XCollection<?> input) 
    { 
     Class<? extends Collection> clazz = input.getClazz(); 
     Collection collection = null; 
     try 
     { 
      collection = clazz.newInstance(); 
      List dataList = input.getList(); 
      for (Object object : dataList) 
      { 
       collection.add(dexmlizeNestedStructure(object)); 
      } 
     } 
     catch (Exception e) 
     { 
      e.printStackTrace(); 
     } 
     return collection; 
    } 

    private static Object dexmlizeNestedMap(XMap<?, ?> input) 
    { 
     Class<? extends Map> clazz = input.getClazz(); 
     Map map = null; 
     try 
     { 
      map = clazz.newInstance(); 
      List<? extends XEntry> entryList = input.getList(); 
      for (XEntry xEntry : entryList) 
      { 
       Object key = dexmlizeNestedStructure(xEntry.getKey()); 
       Object value = dexmlizeNestedStructure(xEntry.getValue()); 
       map.put(key, value); 
      } 
     } 
     catch (Exception e) 
     { 
      e.printStackTrace(); 
     } 
     return map; 
    } 

    public static XMap<?, ?> xmlizeNestedMap(Map<?, ?> input) { 
      XMap<Object, Object> ret = new XMap<Object, Object>(input.getClass()); 

      for (Map.Entry<?, ?> e : input.entrySet()) { 
        ret.add(xmlizeNestedStructure(e.getKey()), 
            xmlizeNestedStructure(e.getValue())); 
      } 
      return ret; 
    } 

    public static XCollection<?> xmlizeNestedCollection(Collection<?> input) { 
      XCollection<Object> ret = new XCollection<Object>(input.getClass()); 

      for (Object entry : input) { 
        ret.add(xmlizeNestedStructure(entry)); 
      } 
      return ret; 
    } 

    @XmlType 
    @XmlRootElement 
    public final static class XMap<K, V>{ 
      private List<XEntry<K, V>> list = new ArrayList<XEntry<K, V>>(); 
      private Class<? extends Map> clazz = null; 

      public XMap(Class mapClazz) { 
       this.clazz = (Class<? extends Map>)mapClazz; 
      } 

      public XMap() { 
      } 

      public void add(K key, V value) { 
        list.add(new XEntry<K, V>(key, value)); 
      } 

      @XmlElementWrapper(name = "map") 
      @XmlElement(name = "entry") 
      public List<XEntry<K, V>> getList() 
      { 
       return list; 
      } 

      public void setList(List<XEntry<K, V>> list) 
      { 
       this.list = list; 
      } 

      @XmlElement(name="clazz") 
      public Class<? extends Map> getClazz() 
      { 
       return clazz; 
      } 

      public void setClazz(Class<? extends Map> clazz) 
      { 
       this.clazz = clazz; 
      } 
    } 

    @XmlType 
    @XmlRootElement 
    public final static class XEntry<K, V> { 
      private K key; 
      private V value; 

      private XEntry() { 
      } 

      public XEntry(K key, V value) { 
        this.key = key; 
        this.value = value; 
      } 

      @XmlElement 
      public K getKey() 
      { 
       return key; 
      } 

      public void setKey(K key) 
      { 
       this.key = key; 
      } 

      @XmlElement 
      public V getValue() 
      { 
       return value; 
      } 

      public void setValue(V value) 
      { 
       this.value = value; 
      } 
    } 

    @XmlType 
    @XmlRootElement 
    public final static class XCollection<V> { 
      private List<V> list = new ArrayList<V>(); 
      private Class<? extends Collection> clazz = null; 

      public XCollection(Class collectionClazz) { 
       this.clazz = collectionClazz; 
      } 

      public XCollection() { 
      } 

      public void add(V obj) { 
        list.add(obj); 
      } 

      @XmlElementWrapper(name = "collection") 
      @XmlElement(name = "entry") 
      public List<V> getList() 
      { 
       return list; 
      } 

      public void setList(List<V> list) 
      { 
       this.list = list; 
      } 

      @XmlElement(name="clazz") 
      public Class<? extends Collection> getClazz() 
      { 
       return clazz; 
      } 


      public void setClazz(Class<? extends Collection> clazz) 
      { 
       this.clazz = clazz; 
      } 
    } 

} 
4

miałem ten sam wymóg, aby korzystać z mapy < String, mapa < String, Integer >>. Użyłem XMLAdapter i działało dobrze. Korzystanie z XMLAdaptor jest najczystszym rozwiązaniem, które myślę. Poniżej znajduje się kod adaptera. To jest fragment kodu klasy jaXb.

@XmlJavaTypeAdapter(MapAdapter.class) 
    Map<String, Map<String, Integer>> mapOfMap = new HashMap<String,Map<String, Integer>>(); 

MapType Klasa:

public class MapType { 

public List<MapEntryType> host = new ArrayList<MapEntryType>(); 

} 

MapEntry Rodzaj Klasa:

public class MapEntryType { 

@XmlAttribute 
public String ip; 

@XmlElement 
public List<LinkCountMapType> request_limit = new ArrayList<LinkCountMapType>(); 

} 

LinkCountMapType Klasa:

public class LinkCountMapType { 
@XmlAttribute 
public String service; 

@XmlValue 
public Integer count; 
} 

Wreszcie klasa MapAdaptor:

public final class MapAdapter extends XmlAdapter<MapType, Map<String, Map<String, Integer>>> { 

@Override 
public Map<String, Map<String, Integer>> unmarshal(MapType v) throws Exception { 
    Map<String, Map<String, Integer>> mainMap = new HashMap<String, Map<String, Integer>>(); 

    List<MapEntryType> myMapEntryTypes = v.host; 
    for (MapEntryType myMapEntryType : myMapEntryTypes) { 
     Map<String, Integer> linkCountMap = new HashMap<String, Integer>(); 
     for (LinkCountMapType myLinkCountMapType : myMapEntryType.request_limit) { 
      linkCountMap.put(myLinkCountMapType.service, myLinkCountMapType.count); 
     } 
     mainMap.put(myMapEntryType.ip, linkCountMap); 
    } 
    return mainMap; 
} 

@Override 
public MapType marshal(Map<String, Map<String, Integer>> v) throws Exception { 
    MapType myMapType = new MapType(); 

    List<MapEntryType> entry = new ArrayList<MapEntryType>(); 

    for (String ip : v.keySet()) { 
     MapEntryType myMapEntryType = new MapEntryType(); 
     Map<String, Integer> linkCountMap = v.get(ip); 
     List<LinkCountMapType> linkCountList = new ArrayList<LinkCountMapType>(); 
     for (String link : linkCountMap.keySet()) { 
      LinkCountMapType myLinkCountMapType = new LinkCountMapType(); 
      Integer count = linkCountMap.get(link); 
      myLinkCountMapType.count = count; 
      myLinkCountMapType.service = link; 
      linkCountList.add(myLinkCountMapType); 
     } 
     myMapEntryType.ip = ip; 
     myMapEntryType.request_limit = linkCountList; 
     entry.add(myMapEntryType); 
    } 
    myMapType.host = entry; 
    return myMapType; 
} 

}

rozrządowych Obiektu JAXB da poniższej XML

 <mapOfmap> 
    <host ip="127.0.0.1"> 
     <request_limit service="service1">7</request_limit> 
     <request_limit service="service2">8</request_limit> 
    </host> 
</mapOfmap> 
1

Podczas pracy ze złożonymi strukturami schematu - w JAXB wiążące mogą mieć zasadnicze znaczenie w rozwiązywaniu skonfliktowany obiektów. Narzędzie CAM Editor na SourceForge pozwala na automatyczne tworzenie powiązań JAXB - patrz Podręcznik szybkiej tutaj po więcej szczegółów - http://www.cameditor.org/#JAXB_Bindings

1

Aby rozwiązać ten problem dla JSON zrobić: jackson with jaxb

<init-param> 
     <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name> 
     <param-value>true</param-value> 
    </init-param> 
+0

Proszę być bardziej opisowym w swojej odpowiedzi.Zobacz: [How to Answer] (http://stackoverflow.com/questions/ how-to-answer) – askmish

+0

To, co piszesz, nie ma sensu, piszesz mapowanie zasobów web.xml ..... To jest pytanie RMI, a nie definicja serwletu –

Powiązane problemy