JAXB: ¿Cómo debo reunir estructuras de datos anidados complejos?
-
03-07-2019 - |
Pregunta
Tengo varias estructuras de datos complejas como
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)
Nota: en mi caso, realmente no importa si uso Set o List.
Ahora sé que JAXB me permite 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 objetivo declarando dos adaptadores Xml generalizadores:
- uno para Mapa:
MapAdapter<K,V>
- uno para Set:
SetAdapter<V>
El problema :
JAXB se queja de la siguiente manera:
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<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");
}
}
}
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<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());
}
}
Solución
He resuelto el problema sin XmlAdapter's .
He escrito objetos con anotaciones JAXB para Mapa , Map.Entry y Colección .
La idea principal está dentro del método xmlizeNestedStructure (...) :
Echa 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<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);
}
}
}
¡Funciona!
Veamos una salida de demostración :
<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>
Lo sentimos, la salida de demostración también utiliza una estructura de datos llamada " count " 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) atributos xsi: type innecesarios?
Otros consejos
Tenía el mismo requisito para usar un Mapa < Cadena, Mapa & Lt; Cadena, entero & Gt; & Gt ;. Usé el XMLAdapter y funcionó bien. Usar XMLAdaptor es la solución más limpia, creo. A continuación se muestra el código del adaptador. Este es el fragmento de código de clase jaXb.
@XmlJavaTypeAdapter(MapAdapter.class)
Map<String, Map<String, Integer>> mapOfMap = new HashMap<String,Map<String, Integer>>();
Clase MapType:
public class MapType {
public List<MapEntryType> host = new ArrayList<MapEntryType>();
}
Clase de tipo MapEntry:
public class MapEntryType {
@XmlAttribute
public String ip;
@XmlElement
public List<LinkCountMapType> request_limit = new ArrayList<LinkCountMapType>();
}
Clase LinkCountMapType:
public class LinkCountMapType {
@XmlAttribute
public String service;
@XmlValue
public Integer count;
}
Finalmente la clase 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;
}
}
Al ordenar un objeto Jaxb se obtendrá el siguiente XML
<mapOfmap>
<host ip="127.0.0.1">
<request_limit service="service1">7</request_limit>
<request_limit service="service2">8</request_limit>
</host>
</mapOfmap>
A continuación se muestra el código con " dexmlize " habilidad basada en el código de Ivan anterior. Uso:
Map<String, List> nameMapResult = (Map<String, List>) Adapters.dexmlizeNestedStructure(unmarshallResult);
Para restaurar la colección y la clase de mapa, se xmlizará 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<? 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;
}
}
}
Parece que estás en el camino correcto con XMLAdapter ... el mensaje de error puede ser una pista:
clase java.util.Collections $ UnmodifiableMap ni ninguno de su superclase es conocido por este contexto.
¿estás envolviendo un mapa usando Collections.unmodifiableMap () en algún lugar? ¿Dónde se produce exactamente el error?
(la respuesta anterior fue un registro obsoleto para los curiosos)
Puede crear una lógica personalizada de clasificación / descifración que funcione un poco más directa que la idea de Adaptadores (creo; no he usado esa 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 tiene que hacerlo). 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 estaba haciendo 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:
<?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 y printMethod convierten a / desde cadenas de atributos)
Aquí está mi marshaller / unmarshaller para la lista de la clase @XmlType.
Por ejemplo
//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;
}
Cuando se trabaja 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 enlaces JAXB automáticamente; consulte la guía rápida aquí para obtener más detalles: http: / /www.cameditor.org/#JAXB_Bindings
Para arreglar esto para JSON haz: jackson con jaxb
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>