web-dev-qa-db-ja.com

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を定義したくありません(コピーが多すぎて、コードを貼り付けます)。

私は2つの一般化XmlAdaptersを宣言することで目標を達成しようとしました。

  • マップ用:MapAdapter<K,V>
  • セット用: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());
}

}

私は問題を解決しましたXmlAdapterなし

MapMap.Entry、およびCollectionのJAXB注釈付きオブジェクトを作成しました。
主なアイデアはメソッド内にあります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

Map <String、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>
5
Hunaid Husain

以下は、上記のIvanのコードに基づいた「dexmlize」機能を持つコードです。使用法:

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

コレクションおよびマップクラスを復元​​するために、クラス情報を記録するために新しいフィールドがXML化されます。詳細コード:

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
Henry

複雑なスキーマ構造を使用する場合-競合するオブジェクトを解決するには、JAXBバインディングが重要になる場合があります。 SourceforgeのCAM Editorツールを使用すると、JAXBバインディングを自動的に作成できます-詳細については、こちらのクイックガイドを参照してください- http://www.cameditor.org/#JAXB_Bindings

1
user841942

JSONでこれを修正するには: jackson with jaxb

<init-param>
        <param-name>com.Sun.jersey.api.json.POJOMappingFeature</param-name>
        <param-value>true</param-value>
    </init-param>
1
Chris

@XmlTypeクラスのリストの私のmarshaller/unmarshallerを以下に示します。

例えば

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

XMLAdapterで正しい軌道に乗っているように見えます...エラーメッセージが手掛かりかもしれません:

java.util.Collections $ UnmodifiableMapクラスもそのスーパークラスもこのコンテキストで認識されています。

collections.unmodifiableMap()を使用してどこでもマップをラップしていますか?エラーはどこで発生しますか?


(好奇心のための古い記録として残された以前の回答)

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は属性文字列との間で変換します)

1
Jason S