JAXB:如何将地图编组为<key> value </ key>

Tim*_*mur 64 java xml jaxb jaxb2

问题是关于JAXB Map编组 - 有很多关于如何将Map转换为如下结构的示例:

<map>
  <entry>
    <key> KEY </key>
    <value> VALUE </value>
  </entry>
  <entry>
    <key> KEY2 </key>
    <value> VALUE2 </value>
  </entry>
  <entry>
  ...
</map>
Run Code Online (Sandbox Code Playgroud)

实际上,这是JAXB原生支持的.但是,我需要的是XML,其中key是元素名称,value是其内容:

<map>
  <key> VALUE </key>
  <key2> VALUE2 </key2>
 ...
</map>
Run Code Online (Sandbox Code Playgroud)

我没有按照JAXB开发人员推荐的方式(https://jaxb.dev.java.net/guide/Mapping_your_favorite_class.html)成功实现我的Map适配器,因为我需要,他 - 动态属性名称:)

那有什么解决方案吗?

PS目前我必须为每个我想要编组的典型键值对创建一个专用的容器类 - 它可以工作,但是我必须创建太多这些辅助容器.

Jus*_*owe 28

您可能有正当理由要这样做,但通常最好避免生成此类XML.为什么?因为这意味着地图的XML元素依赖于地图的运行时内容.由于XML通常用作外部接口或接口层,因此这是不可取的.让我解释.

Xml Schema(xsd)定义XML文档的接口契约.除了能够从XSD生成代码之外,JAXB还可以从代码中为您生成XML模式.这允许您将通过接口交换的数据限制为XSD中定义的预先约定的结构.

在a的默认情况下Map<String, String>,生成的XSD将限制map元素包含多个条目元素,每个条目元素必须包含一个xs:string键和一个xs:string值.这是一个非常明确的接口合同.

您所描述的是您希望xml映射包含其名称将在运行时由地图内容确定的元素.然后生成的XSD只能指定映射必须包含在编译时类型未知的元素列表.在定义接口契约时,您通常应该避免这种情况.

要在这种情况下实现严格的约定,您应该使用枚举类型作为映射的键而不是String.例如

public enum KeyType {
 KEY, KEY2;
}

@XmlJavaTypeAdapter(MapAdapter.class)
Map<KeyType , String> mapProperty;
Run Code Online (Sandbox Code Playgroud)

这样,您希望在XML中成为XML元素的键在编译时就已知,因此JAXB应该能够生成一个模式,该模式将使用预定义键KEY或KEY2之一将map元素限制为元素.

另一方面,如果您希望简化默认生成的结构

<map>
    <entry>
        <key>KEY</key>
        <value>VALUE</value>
    </entry>
    <entry>
        <key>KEY2</key>
        <value>VALUE2</value>
    </entry>
</map>
Run Code Online (Sandbox Code Playgroud)

对于这样简单的事情

<map>
    <item key="KEY" value="VALUE"/>
    <item key="KEY2" value="VALUE2"/>
</map>
Run Code Online (Sandbox Code Playgroud)

您可以使用MapAdapter将Map转换为MapElements数组,如下所示:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)


Gré*_*ory 22

提供的代码对我不起作用.我发现了另一种Map方式:

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;
  }
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

我在这个网站上找到了代码:http: //www.developpez.net/forums/d972324/java/general-java/xml/hashmap-jaxb/

  • 我不确定这是如何得到这么多的选票,因为它没有做到所要求的.这仍然返回`<key>键</ key> <value>值</ value>`.不是`<key> value </ key>`.我已经复制并调整了这段代码.这是"最佳解决方案"吗? (13认同)
  • 这不是解决所述问题的方法. (3认同)

bdo*_*han 15

我仍在研究更好的解决方案,但使用MOXy JAXB,我已经能够处理以下XML:

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <mapProperty>
      <map>
         <key>value</key>
         <key2>value2</key2>
      </map>
   </mapProperty>
</root>
Run Code Online (Sandbox Code Playgroud)

您需要在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;
    }

}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

AdpatedMap类是所有魔术发生的地方,我们将使用DOM来表示内容.我们将通过组合@XmlAnyElement和Object类型的属性来欺骗JAXB介绍处理DOM:

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

}
Run Code Online (Sandbox Code Playgroud)

此解决方案需要MOXy JAXB实现.您可以通过使用以下条目在模型类中添加名为jaxb.properties的文件来配置JAXB运行时以使用MOXy实现:

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Run Code Online (Sandbox Code Playgroud)

以下演示代码可用于验证代码:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 10

我没有看到任何真正回答这个问题的事情.我找到了一些在这里工作得很好的东西:

使用JAXB XMLAnyElement类型的样式来返回动态元素名称

我修改了一下以支持hashmap树.您可以添加其他集合.

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;
}
Run Code Online (Sandbox Code Playgroud)

然后实现它:

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;
}
Run Code Online (Sandbox Code Playgroud)

通过一个简单的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>
Run Code Online (Sandbox Code Playgroud)