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


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 dejó definir XmlAdapterEstá 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 Generalizing XmlAdapters:

  • uno para el Mapa: MapAdapter<K,V>
  • uno para Set: SetAdapter<V>

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

}

32
2018-05-03 23:31


origen


Respuestas:


He resuelto el problema sin XmlAdapter's.

He escrito objetos anotados por JAXB para Mapa, Map.Entry y Colección.
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<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 un 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 usa también una estructura de datos llamada "contar" 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) innecesario xsi: tipo atributos?


21
2018-05-04 00:19



Tenía el mismo requisito para usar un mapa <String, Map <String, Integer >>. 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<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>

5
2018-04-16 14:33



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

Map<String, List> nameMapResult = (Map<String, List>) 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<? 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;
            }
    }

}

2
2017-07-22 02:03



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

clase   java.util.Collections $ UnmodifiableMap   ni ninguna de su superclase es conocida por   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.


1
2018-05-03 23:37



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

1
2017-07-14 09:17



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


1
2018-05-31 14:35



Para solucionar esto, haga lo siguiente con JSON: jackson con jaxb

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

1
2018-06-29 15:14