JAXB:如何将marshall映射到<key>值</ key>

问题是关于JAXB Map marshalling – 有很多关于如何将Map映射成如下结构的例子:

<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开发人员推荐的方式( https://jaxb.dev.java.net/guide/Mapping_your_favorite_class.html ),因为我需要,他 – dynamic属性名称:)

有没有解决scheme?

PS目前我必须为每个典型的键值对创build一个专用的容器类,我想编组到XML – 它的工作原理,但我必须创造太多的这些辅助容器的方式。

提供的代码不适合我。 我发现另一种方式来映射:

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

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

为什么要这样做可能有一个合理的原因,但通常最好避免生成这种types的XML。 为什么? 因为这意味着地图的XML元素依赖于地图的运行时内容。 而且由于XML通常用作外部接口或接口层,所以这是不可取的。 让我解释。

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

Map<String, String>的默认情况下,生成的XSD将限制map元素包含多个entry元素,每个元素必须包含一个xs:string键和一个xs:string值。 这是一个非常清晰的界面合同。

你所描述的是,你希望xml映射包含元素的名字将由运行时的地图的内容决定。 然后,生成的XSD只能指定映射必须包含编译时types未知的元素列表。 定义接口合同时,通常应该避免这种情况。

为了在这种情况下实现一个严格的契约,你应该使用枚举types作为映射关键字而不是String。 例如

 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转换为MapElement数组的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; } } 

我仍然在研究更好的解决scheme,但使用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和Objecttypes的属性来欺骗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; } } 

该解决scheme需要MOXy JAXB实现。 您可以configurationJAXB运行时使用MOXy实现,方法是在模型类中添加一个名为jaxb.properties的文件,其中包含以下条目:

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

以下演示代码可用于validation代码:

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

我没有看到任何真正的答案。 我发现这里工作得很好:

使用JAXB XMLAnyElementtypes的样式来返回dynamic元素名称

我修改了一下,以支持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; } 

然后执行它:

 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> 

(对不起,不能添加评论)

在Blaise的回答中,如果你改变了:

 @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 ,直接将MapAdapter更改为一个Document对象。 我只用编组testing了这个,所以可能会有编组问题。

我会试着找出时间来敲一个完整的例子,并相应地编辑这个post。

我没有适配器的解决scheme。 将瞬态映射转换为xml元素,反之亦然:

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

您可以使用元素名称/值对。 我需要属性…玩得开心!

似乎这个问题是与另一个重复,我已经收集了一些元帅/解组成一个职位。 您可以在这里查看: 使用JAXB的dynamic标签名称 。

简而言之:

  1. 应该创build一个@xmlAnyElement的容器类
  2. XmlAdapter可以与@XmlJavaTypeAdapter配对使用,在容器类和Map <>之间进行转换;

我find了最简单的解决scheme

 @XmlElement(name="attribute") public String[] getAttributes(){ return attributes.keySet().toArray(new String[1]); } } 

现在它会产生像你这样的xml输出:

 <attribute>key1<attribute> ... <attribute>keyN<attribute> 

当使用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; } }