JAXB: ¿Cómo debería organizar estructuras complejas de datos nesteds?

Tengo varias estructuras de datos complejas como

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

Nota: en mi caso, realmente no importa si uso Set o List.

Ahora sé que JAXB me dejó definir XmlAdapter , está bien, pero no quiero definir un XmlAdapter para cada una de las estructuras de datos dadas (sería demasiado código de copiar y pegar).

Traté de lograr mi objective declarando dos Generalizing XmlAdapters:

  • uno para Map: MapAdapter
  • uno para Set: SetAdapter

El problema :
JAXB se queja de lo siguiente:

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

Aquí está mi clase de adaptador:

 import java.util.*; import javax.xml.bind.annotation.*; import javax.xml.bind.annotation.adapters.*; public class Adapters { public final static class MapAdapter extends XmlAdapter<MapAdapter.Adapter, Map> { @XmlType @XmlRootElement public final static class Adapter { @XmlElement protected List<MyEntry> key = new LinkedList<MyEntry>(); private Adapter() { } public Adapter(Map original) { for (Map.Entry entry : original.entrySet()) { key.add(new MyEntry(entry)); } } } @XmlType @XmlRootElement public final static class MyEntry { @XmlElement protected K key; @XmlElement protected V value; private MyEntry() { } public MyEntry(Map.Entry original) { key = original.getKey(); value = original.getValue(); } } @Override public Adapter marshal(Map obj) { return new Adapter(obj); } @Override public Map unmarshal(Adapter obj) { throw new UnsupportedOperationException("unmarshalling is never performed"); } } } 

Aquí está mi caso de prueba 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> dataStructure = new HashMap<String, Map>(); Map inner1 = new HashMap(); Map inner2 = new HashMap(); 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()); } } 

He resuelto el problema sin XmlAdapter’s .

Escribí objetos anotados por JAXB para Map , Map.Entry y Collection .
La idea principal está dentro del método xmlizeNestedStructure (…) :

Eche un vistazo al código:

 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 ret = new XMap(); for (Map.Entry e : input.entrySet()) { ret.add(xmlizeNestedStructure(e.getKey()), xmlizeNestedStructure(e.getValue())); } return ret; } public static XCollection xmlizeNestedCollection(Collection input) { XCollection ret = new XCollection(); for (Object entry : input) { ret.add(xmlizeNestedStructure(entry)); } return ret; } @XmlType @XmlRootElement public final static class XMap { @XmlElementWrapper(name = "map") @XmlElement(name = "entry") private List> list = new LinkedList>(); public XMap() { } public void add(K key, V value) { list.add(new XEntry(key, value)); } } @XmlType @XmlRootElement public final static class XEntry { @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 { @XmlElementWrapper(name = "list") @XmlElement(name = "entry") private List list = new LinkedList(); public XCollection() { } public void add(V obj) { list.add(obj); } } } 

¡Funciona!

Veamos un resultado de demostración :

     1 a    a1 a2 a3      2 b    b1 b3 b2      3 c    c1 c2 c3      

Lo sentimos, la salida de demostración usa también una estructura de datos llamada “conteo” que no se menciona en el código fuente del Adaptador.

Por cierto: ¿alguien sabe cómo eliminar todos estos molestos y (en mi caso) innecesarios atributos xsi: tipo ?

Tenía el mismo requisito para usar un mapa >. Usé el XMLAdapter y funcionó bien. Usar XMLAdaptor es la solución más limpia, creo. A continuación está el código del adaptador. Este es el fragmento de código de la clase jaXb.

  @XmlJavaTypeAdapter(MapAdapter.class) Map> mapOfMap = new HashMap>(); 

Clase MapType:

 public class MapType { public List host = new ArrayList(); } 

Clase de tipo MapEntry:

 public class MapEntryType { @XmlAttribute public String ip; @XmlElement public List request_limit = new ArrayList(); } 

Clase LinkCountMapType:

 public class LinkCountMapType { @XmlAttribute public String service; @XmlValue public Integer count; } 

Finalmente, la clase MapAdaptor:

  public final class MapAdapter extends XmlAdapter>> { @Override public Map> unmarshal(MapType v) throws Exception { Map> mainMap = new HashMap>(); List myMapEntryTypes = v.host; for (MapEntryType myMapEntryType : myMapEntryTypes) { Map linkCountMap = new HashMap(); for (LinkCountMapType myLinkCountMapType : myMapEntryType.request_limit) { linkCountMap.put(myLinkCountMapType.service, myLinkCountMapType.count); } mainMap.put(myMapEntryType.ip, linkCountMap); } return mainMap; } @Override public MapType marshal(Map> v) throws Exception { MapType myMapType = new MapType(); List entry = new ArrayList(); for (String ip : v.keySet()) { MapEntryType myMapEntryType = new MapEntryType(); Map linkCountMap = v.get(ip); List linkCountList = new ArrayList(); 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; } 

}

Al ordenar un Objeto Jaxb se obtendrá el siguiente XML

    7 8   

A continuación está el código con la habilidad “dexmlize” basada en el código de Ivan anterior. Uso:

 Map nameMapResult = (Map) Adapters.dexmlizeNestedStructure(unmarshallResult); 

Para restaurar la clase de colección y mapa, se creará un nuevo campo para registrar la información de la clase. Código detallado:

 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 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 clazz = input.getClazz(); Map map = null; try { map = clazz.newInstance(); List 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 ret = new XMap(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 ret = new XCollection(input.getClass()); for (Object entry : input) { ret.add(xmlizeNestedStructure(entry)); } return ret; } @XmlType @XmlRootElement public final static class XMap{ private List> list = new ArrayList>(); private Class clazz = null; public XMap(Class mapClazz) { this.clazz = (Class)mapClazz; } public XMap() { } public void add(K key, V value) { list.add(new XEntry(key, value)); } @XmlElementWrapper(name = "map") @XmlElement(name = "entry") public List> getList() { return list; } public void setList(List> list) { this.list = list; } @XmlElement(name="clazz") public Class getClazz() { return clazz; } public void setClazz(Class clazz) { this.clazz = clazz; } } @XmlType @XmlRootElement public final static class XEntry { 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 { private List list = new ArrayList(); private Class 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 getList() { return list; } public void setList(List list) { this.list = list; } @XmlElement(name="clazz") public Class getClazz() { return clazz; } public void setClazz(Class clazz) { this.clazz = clazz; } } } 

Parece que estás en el camino correcto con XMLAdapter … el mensaje de error puede ser una pista:

la clase java.util.Collections $ UnmodifiableMap o cualquiera de sus superclase se conoce en este contexto.

¿Estás envolviendo un mapa usando Collections.unmodifiableMap () en cualquier lugar? ¿Dónde exactamente ocurre el error?


(respuesta anterior dejó como un registro obsoleto para los curiosos)

Puede crear una lógica personalizada de Marshaller / Unmarshaller que funcione un poco más directa que la idea de Adapters (creo, no la he usado antes).

Básicamente, la idea es que especifique una función estática para hacer el trabajo, y también puede crear una clase personalizada. (Normalmente pongo la función estática en la clase en cuestión, pero no es necesario). Luego, coloca una línea en su archivo .XJB para indicarle a JAXB que use su función estática.

Ahora que eché un vistazo a mi código existente, veo que todo lo que hacía era convertir una cadena de atributos en un objeto Java personalizado. Aquí está el código, como referencia, pero es solo para atributos.

Archivo JAXB:

               

(parseMethod e printMethod convertir a / desde cadenas de atributos)

Aquí está mi marshaller / unmarshaller para la lista de la clase @XmlType.

P.ej

 //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 { @XmlElement private List value ; public ListHodler() { } public ListHodler(List value) { this.value = value; } public List getValue() { if(value == null) value = new ArrayList(); return this.value; } } //marshall collection of T public static  void marshallXmlTypeCollection(List value, Class clzz, OutputStream os) { try { ListHodler holder = new ListHodler(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  List unmarshallXmlTypeCollection(Class clzz, InputStream input) { try { JAXBContext context = JAXBContext.newInstance(ListHodler.class, clzz); Unmarshaller u = context.createUnmarshaller(); ListHodler holder = (ListHodler) u.unmarshal(new StreamSource(input)); return holder.getValue(); } catch (JAXBException e) { e.printStackTrace(); } return null; } 

Al trabajar con estructuras de esquema complejas, el enlace JAXB puede ser crucial para resolver objetos en conflicto. La herramienta CAM Editor en Sourceforge le permite crear automáticamente enlaces JAXB; consulte la guía rápida aquí para obtener más detalles: http://www.cameditor.org/#JAXB_Bindings

Para arreglar esto para JSON do: jackson con jaxb

  com.sun.jersey.api.json.POJOMappingFeature true