Подтвердить что ты не робот

JAXB: Как мне объединить сложные вложенные структуры данных?

У меня есть несколько сложных структур данных, таких как

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)

Примечание. В моем случае это не имеет значения, если я использую Set или List.

Теперь я знаю, что JAXB позволил мне определить XmlAdapter, это прекрасно, но я не хочу определять XmlAdapter для каждой из данных структур данных (это будет слишком много кода копирования и вставки).

Я попытался достичь своей цели, объявив два обобщающих XmlAdapters:

  • один для карты: MapAdapter<K,V>
  • один для Set: SetAdapter<V>

Проблема:
JAXB жалуется на следующее:

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

Вот мой класс адаптера:

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

}

}

Вот мой тестовый пример 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());
}

}
4b9b3361

Ответ 1

Я решил проблему без XmlAdapter.

Я написал JAXB-аннотированные объекты для Карта, Map.Entry и Коллекция.
Основная идея заключается в методе xmlizeNestedStructure (...):

Взгляните на код:

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

}

}

Он работает!

Посмотрите на демонстрационный вывод :

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

Извините, демонстрационный вывод использует также структуру данных, называемую "count" который не упоминается в исходном коде адаптера.

BTW: Кто-нибудь знает, как удалить все эти раздражающие и (в моем случае) ненужные атрибуты xsi: type?

Ответ 2

У меня было такое же требование использовать Map < Строка, Карта < String, Integer → . Я использовал XMLAdapter, и он работал нормально. Я считаю, что использование XMLAdaptor - самое чистое решение. Ниже приведен код адаптера. Это фрагмент кода класса jaXb.

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

Класс MapType:

public class MapType {

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

}

MapEntry Тип Класс:

public class MapEntryType {

@XmlAttribute
public String ip;

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

}

LinkCountMapType Класс:

public class LinkCountMapType {
@XmlAttribute
public String service;

@XmlValue
public Integer count;
}

Наконец, класс 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;
}

}

Марширование объекта Jaxb даст ниже XML

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

Ответ 3

Ниже приведен код с возможностью "dexmlize", основанный на вышеуказанном коде Ивана. Использование:

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

Чтобы восстановить класс коллекции и карты, новое поле будет xmlized для записи информации о классе. Подробный код:

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

Похоже, что вы на правильном пути с XMLAdapter... сообщение об ошибке может быть ключом:

класс java.util.Collections $UnmodifiableMap ни один из его суперкласса не известен этот контекст.

Вы обертываете карту, используя Collections.unmodifiableMap() где угодно? Где именно происходит ошибка?


(предыдущий ответ оставлен как устаревшая запись для любопытных)

Вы можете создать собственную логику marshaller/unmarshaller, которая работает немного больше, чем идея Adapters (я думаю, я не использовал ее раньше).

В основном идея состоит в том, что вы указываете статическую функцию для выполнения работы, и вы также можете создать собственный класс. (Обычно я ставил статическую функцию в рассматриваемый класс, но вам этого не нужно.) Затем вы помещаете строку в ваш .XJB файл, чтобы сообщить JAXB использовать вашу статическую функцию.

Теперь, когда я взглянул на свой существующий код, я вижу, что все, что я делал, это преобразование строки атрибута в пользовательский объект Java. Вот код, для справки, но он просто для атрибутов.

Файл 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 и printMethod конвертировать в/из строк атрибутов)

Ответ 5

Вот мой маршаллер /unmarshaller для списка класса @XmlType.

например

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

Ответ 6

При работе со сложными структурами схемы привязка JAXB может иметь решающее значение для разрешения конфликтующих объектов. Инструмент CAM Editor на Sourceforge позволяет вам автоматически создавать привязки JAXB - см. Краткое руководство для более подробной информации - http://www.cameditor.org/#JAXB_Bindings

Ответ 7

Чтобы исправить это для JSON, выполните: jackson с jaxb

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