问题是关于JAXB Map编组 - 有很多关于如何将Map安装到如下结构中的示例:
<map>
<entry>
<key> KEY </key>
<value> VALUE </value>
</entry>
<entry>
<key> KEY2 </key>
<value> VALUE2 </value>
</entry>
<entry>
...
</map>
事实上,JAXB本身支持这一点。然而,我需要的是XML,其中key是元素名称,value是其内容:
<map>
<key> VALUE </key>
<key2> VALUE2 </key2>
...
</map>
我没有按照JAXB开发人员(https://jaxb.dev.java.net/guide/Mapping_your_favorite_class.html)推荐的方式成功实现我的Map适配器,因为我需要,他 - 动态属性名称:)
有没有解决方案?
P.S。目前,我必须为每个我希望编组为XML的典型键值对创建一个专用容器类 - 它可以工作,但是我必须创建太多这些辅助容器。
答案 0 :(得分:27)
可能有一个正当理由要执行此操作,但通常最好避免生成此类XML。为什么?因为这意味着地图的XML元素依赖于地图的运行时内容。由于XML通常用作外部接口或接口层,因此这是不可取的。让我解释一下。
Xml Schema(xsd)定义XML文档的接口契约。除了能够从XSD生成代码之外,JAXB还可以从代码中为您生成XML模式。这允许您将通过接口交换的数据限制为XSD中定义的预先约定的结构。
在Map<String, String>
的默认情况下,生成的XSD将限制map元素包含多个条目元素,每个条目元素必须包含一个xs:string
键和一个xs:string
值。这是一个非常明确的接口合同。
您所描述的是您希望xml映射包含其名称将在运行时由地图内容确定的元素。然后生成的XSD只能指定映射必须包含在编译时类型未知的元素列表。在定义接口契约时,您通常应该避免这种情况。
要在这种情况下实现严格的约定,您应该使用枚举类型作为映射的键而不是String。 E.g。
public enum KeyType {
KEY, KEY2;
}
@XmlJavaTypeAdapter(MapAdapter.class)
Map<KeyType , String> mapProperty;
这样,您希望成为XML元素的键在编译时就已知,因此JAXB应该能够生成一个模式,该模式使用预定义键KEY或KEY2之一将map元素限制为元素。
另一方面,如果您希望简化默认生成的结构
<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>
您可以使用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;
}
}
答案 1 :(得分:21)
提供的代码对我不起作用。 我找到了另一种地图方式:
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/
答案 2 :(得分:15)
我仍在研究更好的解决方案,但使用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类型的属性来欺骗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;
}
}
此解决方案需要MOXy JAXB实现。您可以通过使用以下条目在模型类中添加名为jaxb.properties的文件来配置JAXB运行时以使用MOXy实现:
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);
}
}
答案 3 :(得分:10)
我没有看到任何真正能够解决这个问题的事情。 我在这里发现了一些非常好的东西:
Use JAXB XMLAnyElement type of style to return dynamic element names
我修改了一下以支持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>
答案 4 :(得分:3)
(抱歉,无法添加评论)
在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
对象。我只用编组测试了这个,所以可能会有解组问题。
我会尽力找到时间来详细解释这个问题,然后相应地编辑这篇文章。
答案 5 :(得分:2)
我有没有适配器的解决方案。瞬态映射转换为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}}
您可以使用元素名称/值对。我需要属性...... 玩得开心!
答案 6 :(得分:2)
似乎这个问题与另一个问题有点重复,我在一个帖子中收集了一些编组/解组解决方案。您可以在此处查看:Dynamic tag names with JAXB。
简而言之:
@xmlAnyElement
的容器类XmlAdapter
可与@XmlJavaTypeAdapter
配对使用
在容器类和Map&lt;&gt;; 答案 7 :(得分:0)
使用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;
}
}
答案 8 :(得分:0)
Jackson有一个XmlMapper,可以立即支持它,根本不需要编写任何代码。
这是一个很好的教程 https://www.baeldung.com/jackson-xml-serialization-and-deserialization
Maven依赖项:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.8</version>
</dependency>
用于将Map
写入xml:
Map<String, String> map = new HashMap<>();
map.put("SomeKey", "someValue");
XmlMapper mapper = new XmlMapper();
String xml = mapper.writeValueAsString(map);
会给你
<HashMap><SomeKey>someValue</SomeKey></HashMap>
我能够通过创建HashMap
子类来自定义根元素
@JacksonXmlRootElement(localName = "MyRootElement")
public class XmlHashMap<K, V> extends HashMap<K, V>
{
}
所以现在
Map<String, String> map = new XmlHashMap<>();
map.put("SomeKey", "someValue");
XmlMapper mapper = new XmlMapper();
String xml = mapper.writeValueAsString(map);
会给你
<MyRootElement><SomeKey>someValue</SomeKey></MyRootElement>
答案 9 :(得分:0)
大多数人都提到过这里只有 marshalling
是 marshalling
和 unmarshalling
和 Map<String,Object>
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.annotation.XmlAnyElement;
import jakarta.xml.bind.annotation.adapters.XmlAdapter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.xml.namespace.QName;
import lombok.Getter;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class MoxyMapAdapter extends XmlAdapter<MapWrapper, Map<String, Object>> {
@Override
public Map<String, Object> unmarshal(MapWrapper value) {
final Map<String, Object> extensions = new HashMap<>();
//Loop across all elements within value
for (Object obj : value.getElements()) {
Element element = (Element) obj;
final NodeList children = element.getChildNodes();
if (children.getLength() == 1) {
extensions.put(element.getNodeName(), element.getTextContent());
} else {
List<Object> child = new ArrayList<>();
for (int i = 0; i < children.getLength(); i++) {
final Node n = children.item(i);
if (n.getNodeType() == Node.ELEMENT_NODE) {
MapWrapper wrapper = new MapWrapper();
List childElements = new ArrayList();
childElements.add(n);
wrapper.elements = childElements;
child.add(unmarshal(wrapper));
}
}
extensions.put(element.getNodeName(), child);
}
}
return extensions;
}
@Override
public MapWrapper marshal(Map<String, Object> extensions) throws Exception {
if (extensions == null) {
return null;
}
MapWrapper wrapper = new MapWrapper();
List elements = new ArrayList();
//Loop through the Extensions MAP
for (Map.Entry<String, Object> property : extensions.entrySet()) {
//If the Value type is MAP then recurse through the loop
if (property.getValue() instanceof Map) {
elements
.add(new JAXBElement<MapWrapper>(new QName(namespaceURI, localPart, prefix), MapWrapper.class, marshal((Map) property.getValue())));
} else if (property.getValue() instanceof String) {
// If the Value type is String then directly create JAXBElement
elements.add(new JAXBElement<String>(new QName(namespaceURI, localPart, prefix), String.class, property.getValue().toString()));
} else if (property.getValue() instanceof ArrayList) {
//If the Value type is ArrayList then it contains Duplicate key values so Loop through it
for (Object dupItems : (ArrayList<String>) property.getValue()) {
if (dupItems instanceof Map) {
elements.add(new JAXBElement<MapWrapper>(new QName(namespaceURI, localPart, prefix), MapWrapper.class, marshal((Map) dupItems)));
} else {
elements.add(new JAXBElement<String>(new QName(namespaceURI, localPart, prefix), String.class, dupItems.toString()));
}
}
}
}
wrapper.elements = elements;
return wrapper;
}
}
class MapWrapper {
@Getter
@XmlAnyElement
List elements;
}
答案 10 :(得分:-1)
我找到了最简单的解决方案。
@XmlElement(name="attribute")
public String[] getAttributes(){
return attributes.keySet().toArray(new String[1]);
}
}
现在它将在你的xml输出中生成如下:
<attribute>key1<attribute>
...
<attribute>keyN<attribute>