web-dev-qa-db-ja.com

JAXB:マップを<key> value </ key>にマーシャリングする方法

質問はJAXBマップのマーシャリングに関するものです。マップを次のような構造にマーシャリングする方法の例はたくさんあります。

<map>
  <entry>
    <key> KEY </key>
    <value> VALUE </value>
  </entry>
  <entry>
    <key> KEY2 </key>
    <value> VALUE2 </value>
  </entry>
  <entry>
  ...
</map>

実際、これはJAXBによってネイティブにサポートされています。ただし、必要なのはXMLで、キーは要素名、値はそのコンテンツです。

<map>
  <key> VALUE </key>
  <key2> VALUE2 </key2>
 ...
</map>

JAXB開発者が推奨する方法でMapアダプターを実装できませんでした( https://jaxb.dev.Java.net/guide/Mapping_your_favorite_class.html )、必要に応じて、彼は-動的属性名:)

その解決策はありますか?

追伸現在、XMLにマーシャリングするキーと値のペアの典型的なセットごとに専用のコンテナクラスを作成する必要があります-動作しますが、これらのヘルパーコンテナを非常に多く作成する必要があります。

64
Timur

提供されたコードは私にとってはうまくいきませんでした。私はマップする別の方法を見つけました:

MapElements:

package com.cellfish.mediadb.rest.lucene;

import javax.xml.bind.annotation.XmlElement;

class MapElements
{
  @XmlElement public String  key;
  @XmlElement public Integer value;

  private MapElements() {} //Required by JAXB

  public MapElements(String key, Integer value)
  {
    this.key   = key;
    this.value = value;
  }
}

MapAdapter:

import Java.util.HashMap;
import Java.util.Map;

import javax.xml.bind.annotation.adapters.XmlAdapter;

class MapAdapter extends XmlAdapter<MapElements[], Map<String, Integer>> {
    public MapElements[] marshal(Map<String, Integer> arg0) throws Exception {
        MapElements[] mapElements = new MapElements[arg0.size()];
        int i = 0;
        for (Map.Entry<String, Integer> entry : arg0.entrySet())
            mapElements[i++] = new MapElements(entry.getKey(), entry.getValue());

        return mapElements;
    }

    public Map<String, Integer> unmarshal(MapElements[] arg0) throws Exception {
        Map<String, Integer> r = new HashMap<String, Integer>();
        for (MapElements mapelement : arg0)
            r.put(mapelement.key, mapelement.value);
        return r;
    }
}

RootElement:

import Java.util.HashMap;
import Java.util.Map;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
public class Root {

    private Map<String, Integer> mapProperty;

    public Root() {
        mapProperty = new HashMap<String, Integer>();
    }

    @XmlJavaTypeAdapter(MapAdapter.class)
    public Map<String, Integer> getMapProperty() {
        return mapProperty;
    }

    public void setMapProperty(Map<String, Integer> map) {
        this.mapProperty = map;
    }

}

このウェブサイトでコードを見つけました: http://www.developpez.net/forums/d972324/Java/general-Java/xml/hashmap-jaxb/

21
Grégory

これを行うには正当な理由があるかもしれませんが、この種のXMLを生成することは一般的に避けるのが最善です。どうして?というのは、マップのXML要素はマップのランタイムコンテンツに依存しているからです。通常、XMLは外部インターフェイスまたはインターフェイスレイヤーとして使用されるため、これは望ましくありません。説明させてください。

Xmlスキーマ(xsd)は、XMLドキュメントのインターフェイスコントラクトを定義します。 XSDからコードを生成できることに加えて、JAXBはコードからXMLスキーマを生成することもできます。これにより、インターフェイスを介して交換されるデータを、XSDで定義された事前に合意された構造に制限できます。

Map<String, String>のデフォルトの場合、生成されたXSDは、それぞれが1つのxs:stringキーと1つのxs:string値を含む必要がある複数のエントリ要素を含むようにマップ要素を制限します。これは非常に明確なインターフェイスコントラクトです。

説明するのは、実行時にマップのコンテンツによって名前が決定される要素をXMLマップに含めることです。生成されたXSDは、マップがコンパイル時にタイプが不明な要素のリストを含む必要があることのみを指定できます。これは、インターフェイスコントラクトを定義するときに一般的に避けるべきものです。

この場合に厳密なコントラクトを実現するには、文字列ではなく列挙型をマップのキーとして使用する必要があります。例えば。

public enum KeyType {
 KEY, KEY2;
}

@XmlJavaTypeAdapter(MapAdapter.class)
Map<KeyType , String> mapProperty;

そうすれば、XMLの要素になりたいキーはコンパイル時に認識されるため、JAXBは、事前定義されたキーKEYまたはKEY2のいずれかを使用して、マップの要素を要素に制限するスキーマを生成できるはずです。

一方、デフォルトで生成された構造を単純化する場合

<map>
    <entry>
        <key>KEY</key>
        <value>VALUE</value>
    </entry>
    <entry>
        <key>KEY2</key>
        <value>VALUE2</value>
    </entry>
</map>

このような簡単なものに

<map>
    <item key="KEY" value="VALUE"/>
    <item key="KEY2" value="VALUE2"/>
</map>

次のように、MapをMapElementsの配列に変換するMapAdapterを使用できます。

class MapElements {
    @XmlAttribute
    public String key;
    @XmlAttribute
    public String value;

    private MapElements() {
    } //Required by JAXB

    public MapElements(String key, String value) {
        this.key = key;
        this.value = value;
    }
}


public class MapAdapter extends XmlAdapter<MapElements[], Map<String, String>> {
    public MapAdapter() {
    }

    public MapElements[] marshal(Map<String, String> arg0) throws Exception {
        MapElements[] mapElements = new MapElements[arg0.size()];
        int i = 0;
        for (Map.Entry<String, String> entry : arg0.entrySet())
            mapElements[i++] = new MapElements(entry.getKey(), entry.getValue());

        return mapElements;
    }

    public Map<String, String> unmarshal(MapElements[] arg0) throws Exception {
        Map<String, String> r = new TreeMap<String, String>();
        for (MapElements mapelement : arg0)
            r.put(mapelement.key, mapelement.value);
        return r;
    }
}
27
Justin Rowe

私はまだより良いソリューションに取り組んでいますが、 MOXy JAXB を使用して、次のXMLを処理できました。

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <mapProperty>
      <map>
         <key>value</key>
         <key2>value2</key2>
      </map>
   </mapProperty>
</root>

Mapプロパティで@XmlJavaTypeAdapterを使用する必要があります。

import Java.util.HashMap;
import Java.util.Map;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
public class Root {

    private Map<String, String> mapProperty;

    public Root() {
        mapProperty = new HashMap<String, String>();
    }

    @XmlJavaTypeAdapter(MapAdapter.class)
    public Map<String, String> getMapProperty() {
        return mapProperty;
    }

    public void setMapProperty(Map<String, String> map) {
        this.mapProperty = map;
    }

}

XmlAdapterの実装は次のとおりです。

import Java.util.HashMap;
import Java.util.Map;
import Java.util.Map.Entry;

import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class MapAdapter extends XmlAdapter<AdaptedMap, Map<String, String>> {

    @Override
    public AdaptedMap marshal(Map<String, String> map) throws Exception {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document document = db.newDocument();
        Element rootElement = document.createElement("map");
        document.appendChild(rootElement);

        for(Entry<String,String> entry : map.entrySet()) {
            Element mapElement = document.createElement(entry.getKey());
            mapElement.setTextContent(entry.getValue());
            rootElement.appendChild(mapElement);
        }

        AdaptedMap adaptedMap = new AdaptedMap();
        adaptedMap.setValue(document);
        return adaptedMap;
    }

    @Override
    public Map<String, String> unmarshal(AdaptedMap adaptedMap) throws Exception {
        Map<String, String> map = new HashMap<String, String>();
        Element rootElement = (Element) adaptedMap.getValue();
        NodeList childNodes = rootElement.getChildNodes();
        for(int x=0,size=childNodes.getLength(); x<size; x++) {
            Node childNode = childNodes.item(x);
            if(childNode.getNodeType() == Node.ELEMENT_NODE) {
                map.put(childNode.getLocalName(), childNode.getTextContent());
            }
        }
        return map;
    }

}

AdpatedMapクラスは、すべての魔法が発生する場所です。DOMを使用してコンテンツを表します。 @XmlAnyElementとObject型のプロパティを組み合わせて、DOMを扱うJAXBイントロをだます:

import javax.xml.bind.annotation.XmlAnyElement;

public class AdaptedMap {

    private Object value;

    @XmlAnyElement
    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }

}

このソリューションには、MOXy JAXB実装が必要です。次のエントリを持つモデルクラスにjaxb.propertiesという名前のファイルを追加することにより、MOXy実装を使用するようにJAXBランタイムを設定できます。

javax.xml.bind.context.factory=org.Eclipse.persistence.jaxb.JAXBContextFactory

次のデモコードを使用して、コードを検証できます。

import Java.io.File;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Root.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Root root = (Root) unmarshaller.unmarshal(new File("src/forum74/input.xml"));

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(root, System.out);
    }
}
15
bdoughan

私はこれに非常によく答える何も見ませんでした。ここでかなりうまくいくものを見つけました:

スタイルのJAXB XMLAnyElementタイプを使用して動的要素名を返す

ハッシュマップツリーをサポートするために少し変更しました。他のコレクションを追加できます。

public class MapAdapter extends XmlAdapter<MapWrapper, Map<String, Object>> {
    @Override
    public MapWrapper marshal(Map<String, Object> m) throws Exception {
        MapWrapper wrapper = new MapWrapper();
        List elements = new ArrayList();
        for (Map.Entry<String, Object> property : m.entrySet()) {

            if (property.getValue() instanceof Map)
                elements.add(new JAXBElement<MapWrapper>(new QName(getCleanLabel(property.getKey())),
                        MapWrapper.class, marshal((Map) property.getValue())));
            else
                elements.add(new JAXBElement<String>(new QName(getCleanLabel(property.getKey())),
                        String.class, property.getValue().toString()));
        }
        wrapper.elements = elements;
        return wrapper;
    }

    @Override
    public Map<String, Object> unmarshal(MapWrapper v) throws Exception {
        // TODO
        throw new OperationNotSupportedException();
    }

    // Return a XML-safe attribute.  Might want to add camel case support
    private String getCleanLabel(String attributeLabel) {
        attributeLabel = attributeLabel.replaceAll("[()]", "").replaceAll("[^\\w\\s]", "_").replaceAll(" ", "_");
        return attributeLabel;
    }
}
class MapWrapper {
    @XmlAnyElement
    List elements;
}

それを実装するには:

static class myxml {
    String name = "Full Name";
    String address = "1234 Main St";
    // I assign values to the map elsewhere, but it's just a simple
    // hashmap with a hashmap child as an example.
    @XmlJavaTypeAdapter(MapAdapter.class)
    public Map<String, Object> childMap;
}

これを単純なMarshallerにフィードすると、次のような出力が得られます。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<myxml>
    <name>Full Name</name>
    <address>1234 Main St</address>
    <childMap>
        <key2>value2</key2>
        <key1>value1</key1>
        <childTree>
            <childkey1>childvalue1</childkey1>
        </childTree>
    </childMap>
</myxml>
10
JavaJeff

(申し訳ありませんが、コメントを追加できません)

上記のブレイズの答えで、あなたが変更した場合:

@XmlJavaTypeAdapter(MapAdapter.class)
public Map<String, String> getMapProperty() {
    return mapProperty;
}

に:

@XmlJavaTypeAdapter(MapAdapter.class)
@XmlPath(".") // <<-- add this
public Map<String, String> getMapProperty() {
    return mapProperty;
}

その後、これは<mapProperty>タグを取り除く必要があります。

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <map>
        <key>value</key>
        <key2>value2</key2>
    </map>
</root>

交互:

次のように変更することもできます。

@XmlJavaTypeAdapter(MapAdapter.class)
@XmlAnyElement // <<-- add this
public Map<String, String> getMapProperty() {
    return mapProperty;
}

AdaptedMapを完全に削除し、MapAdapterDocumentオブジェクトに直接マーシャルするように変更するだけです。私はマーシャリングでこれをテストしただけなので、アンマーシャリングの問題があるかもしれません。

私はこれの完全な例をノックアップする時間を見つけて、それに応じてこの投稿を編集します。

3
lukens

アダプタなしのソリューションがあります。 xml-elementsに変換された一時的なマップ、およびその逆:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "SchemaBasedProperties")
public class SchemaBasedProperties
{
  @XmlTransient
  Map<String, Map<String, String>> properties;

  @XmlAnyElement(lax = true)
  List<Object> xmlmap;

  public Map<String, Map<String, String>> getProperties()
  {
    if (properties == null)
      properties = new LinkedHashMap<String, Map<String, String>>(); // I want same order

    return properties;
  }

  boolean beforeMarshal(Marshaller m)
  {
    try
    {
      if (properties != null && !properties.isEmpty())
      {
        if (xmlmap == null)
          xmlmap = new ArrayList<Object>();
        else
          xmlmap.clear();

        javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance();
        javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder();
        org.w3c.dom.Document doc = db.newDocument();
        org.w3c.dom.Element element;

        Map<String, String> attrs;

        for (Map.Entry<String, Map<String, String>> it: properties.entrySet())
        {
          element = doc.createElement(it.getKey());
          attrs = it.getValue();

          if (attrs != null)
            for (Map.Entry<String, String> at: attrs.entrySet())
              element.setAttribute(at.getKey(), at.getValue());

          xmlmap.add(element);
        }
      }
      else
        xmlmap = null;
    }
    catch (Exception e)
    {
      e.printStackTrace();
      return false;
    }

    return true;
  }

  void afterUnmarshal(Unmarshaller u, Object p)
  {
    org.w3c.dom.Node node;
    org.w3c.dom.NamedNodeMap nodeMap;

    String name;
    Map<String, String> attrs;

    getProperties().clear();

    if (xmlmap != null)
      for (Object xmlNode: xmlmap)
        if (xmlNode instanceof org.w3c.dom.Node)
        {
          node = (org.w3c.dom.Node) xmlNode;
          nodeMap = node.getAttributes();

          name = node.getLocalName();
          attrs = new HashMap<String, String>();

          for (int i = 0, l = nodeMap.getLength(); i < l; i++)
          {
            node = nodeMap.item(i);
            attrs.put(node.getNodeName(), node.getNodeValue());
          }

          getProperties().put(name, attrs);
        }

    xmlmap = null;
  }

  public static void main(String[] args)
    throws Exception
  {
    SchemaBasedProperties props = new SchemaBasedProperties();
    Map<String, String> attrs;

    attrs = new HashMap<String, String>();
    attrs.put("ResId", "A_LABEL");
    props.getProperties().put("LABEL", attrs);

    attrs = new HashMap<String, String>();
    attrs.put("ResId", "A_TOOLTIP");
    props.getProperties().put("TOOLTIP", attrs);

    attrs = new HashMap<String, String>();
    attrs.put("Value", "hide");
    props.getProperties().put("DISPLAYHINT", attrs);

    javax.xml.bind.JAXBContext jc = javax.xml.bind.JAXBContext.newInstance(SchemaBasedProperties.class);

    Marshaller marshaller = jc.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    marshaller.marshal(props, new Java.io.File("test.xml"));

    Unmarshaller unmarshaller = jc.createUnmarshaller();
    props = (SchemaBasedProperties) unmarshaller.unmarshal(new Java.io.File("test.xml"));

    System.out.println(props.getProperties());
  }
}

予想される私の出力:

<SchemaBasedProperties>
    <LABEL ResId="A_LABEL"/>
    <TOOLTIP ResId="A_TOOLTIP"/>
    <DISPLAYHINT Value="hide"/>
</SchemaBasedProperties>

{LABEL={ResId=A_LABEL}, TOOLTIP={ResId=A_TOOLTIP}, DISPLAYHINT={Value=hide}}

要素の名前と値のペアを使用できます。属性が必要です...楽しんでください!

2
lunicon

この質問は別の質問と重複しているようです。私はいくつかのマーシャル/アンマーシャルソリューションを1つの投稿にまとめました。ここで確認できます: JAXBを使用した動的タグ名

要するに:

  1. @xmlAnyElementのコンテナクラスを作成する必要があります
  2. XmlAdapter@XmlJavaTypeAdapterと組み合わせて使用​​して、コンテナクラスとMap <>の間で変換できます。
2
mec_test_1

Xml-apis-1.0を使用する場合、これをシリアライズおよびデシリアライズできます。

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <map>
        <key>value</key>
        <key2>value2</key2>
    </map>
</root>

このコードの使用:

import Java.io.File;
import Java.util.HashMap;
import Java.util.List;
import Java.util.Map;
import Java.util.Map.Entry;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

@XmlRootElement
class Root {

    public XmlRawData map;

}

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Root.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Root root = (Root) unmarshaller.unmarshal(new File("src/input.xml"));

        System.out.println(root.map.getAsMap());

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(root, System.out);
    }
}

class XmlRawData {

    @XmlAnyElement
    public List<Element> elements;

    public void setFromMap(Map<String, String> values) {

        Document document;
        try {
            document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        } catch (ParserConfigurationException e) {
            throw new RuntimeException(e);
        }

        for (Entry<String, String> entry : values.entrySet()) {
            Element mapElement = document.createElement(entry.getKey());
            mapElement.appendChild(document.createTextNode(entry.getValue()));
            elements.add(mapElement);
        }
    }

    public Map<String, String> getAsMap() {
        Map<String, String> map = new HashMap<String, String>();

        for (Element element : elements) {
            if (element.getNodeType() == Node.ELEMENT_NODE) {
                map.put(element.getLocalName(), element.getFirstChild().getNodeValue());
            }
        }

        return map;
    }
}
0
slartidan