The question is about JAXB Map marshalling - there is plenty of examples on how to marhsall a Map into a structure like follows:
问题是关于JAXB映射编组的——关于如何将所有映射组合成如下结构的示例有很多:
In fact, this is natively supported by JAXB. What I need, however, is the XML where key is the element name, and value is its content:
实际上,这是由JAXB直接支持的。但是,我需要的是XML,其中键是元素名,值是它的内容:
I didn't succeed implementing my Map adapter the way it is recommended by JAXB developers (https://jaxb.dev.java.net/guide/Mapping_your_favorite_class.html), as I need, he - dynamic attribute name :)
我没有按照JAXB开发人员推荐的方式成功实现我的映射适配器(https://jaxb.dev.java.net/guide/Mapping_your_favorite_class.html),因为我需要,he - dynamic属性名:)
Is there any solution for that?
有什么解决办法吗?
P.S. Currently I have to create a dedicated container class for each typical set of key-value pairs I want to marshall to XML - it works, but I have to create way too many of these helper containers.
P.S.目前我必须为我想要交付给XML的每一组典型的键-值对创建一个专用的容器类——它可以工作,但是我必须创建太多的这些帮助容器。
22
the code provided didn't work for me. I found another way to Map :
提供的代码对我不起作用。我找到了另一种地图:
MapElements :
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 :
MapAdapter:
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.annotation.adapters.XmlAdapter;
class MapAdapter extends XmlAdapter> {
public MapElements[] marshal(Map arg0) throws Exception {
MapElements[] mapElements = new MapElements[arg0.size()];
int i = 0;
for (Map.Entry entry : arg0.entrySet())
mapElements[i++] = new MapElements(entry.getKey(), entry.getValue());
return mapElements;
}
public Map unmarshal(MapElements[] arg0) throws Exception {
Map r = new HashMap();
for (MapElements mapelement : arg0)
r.put(mapelement.key, mapelement.value);
return r;
}
}
The rootElement :
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 mapProperty;
public Root() {
mapProperty = new HashMap();
}
@XmlJavaTypeAdapter(MapAdapter.class)
public Map getMapProperty() {
return mapProperty;
}
public void setMapProperty(Map map) {
this.mapProperty = map;
}
}
I found the code in this website : http://www.developpez.net/forums/d972324/java/general-java/xml/hashmap-jaxb/
我在这个网站上找到了代码:http://www.developpez.net/forums/d972324/ java/generaljava/xml/hashmap-jaxb/
25
There may be a valid reason why you want to do this, but generating this kind of XML is generally best avoided. Why? Because it means that the XML elements of your map are dependent on the runtime contents of your map. And since XML is usually used as an external interface or interface layer this is not desirable. Let me explain.
您想这样做可能有一个合理的原因,但是最好避免生成这种XML。为什么?因为这意味着映射的XML元素依赖于映射的运行时内容。由于XML通常用作外部接口或接口层,所以这是不可取的。让我解释一下。
The Xml Schema (xsd) defines the interface contract of your XML documents. In addition to being able to generate code from the XSD, JAXB can also generate the XML schema for you from the code. This allows you to restrict the data exchanged over the interface to the pre-agreed structures defined in the XSD.
Xml模式(xsd)定义了Xml文档的接口契约。除了能够从XSD生成代码之外,JAXB还可以从代码中为您生成XML模式。这允许将在接口上交换的数据限制为XSD中定义的预先约定的结构。
In the default case for a Map
, the generated XSD will restrict the map element to contain multiple entry elements each of which must contain one xs:string
key and one xs:string
value. That's a pretty clear interface contract.
在默认情况下,对于Map
What you describe is that you want the xml map to contain elements whose name will be determined by the content of the map at runtime. Then the generated XSD can only specify that the map must contain a list of elements whose type is unknown at compile time. This is something that you should generally avoid when defining an interface contract.
您所描述的是,您希望xml映射包含的元素的名称将在运行时由映射的内容决定。然后生成的XSD只能指定映射必须包含一个在编译时未知类型的元素列表。在定义接口契约时,通常应该避免这种情况。
To achieve a strict contract in this case, you should use an enumerated type as the key of the map instead of a String. E.g.
要在这种情况下实现严格的契约,应该使用枚举类型作为映射的键,而不是字符串。如。
public enum KeyType {
KEY, KEY2;
}
@XmlJavaTypeAdapter(MapAdapter.class)
Map mapProperty;
That way the keys which you want to become elements in XML are known at compile time so JAXB should be able to generate a schema that would restrict the elements of map to elements using one of the predefined keys KEY or KEY2.
这样,您希望在XML中成为元素的键在编译时就知道了,因此JAXB应该能够生成一个模式,使用预定义的键键键或KEY2将映射的元素限制为元素。
On the other hand, if you wish to simplify the default generated structure
另一方面,如果您希望简化默认生成的结构
To something simpler like this
像这样更简单。
You can use a MapAdapter that converts the Map to an array of MapElements as follows:
您可以使用MapAdapter将映射转换成如下所示的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> {
public MapAdapter() {
}
public MapElements[] marshal(Map arg0) throws Exception {
MapElements[] mapElements = new MapElements[arg0.size()];
int i = 0;
for (Map.Entry entry : arg0.entrySet())
mapElements[i++] = new MapElements(entry.getKey(), entry.getValue());
return mapElements;
}
public Map unmarshal(MapElements[] arg0) throws Exception {
Map r = new TreeMap();
for (MapElements mapelement : arg0)
r.put(mapelement.key, mapelement.value);
return r;
}
}
14
I'm still working on a better solution but using MOXy JAXB, I've been able to handle the following XML:
我仍在研究更好的解决方案,但使用MOXy JAXB,我能够处理以下XML:
You need to use an @XmlJavaTypeAdapter on your Map property:
您需要在映射属性上使用@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 mapProperty;
public Root() {
mapProperty = new HashMap();
}
@XmlJavaTypeAdapter(MapAdapter.class)
public Map getMapProperty() {
return mapProperty;
}
public void setMapProperty(Map map) {
this.mapProperty = map;
}
}
The implementation of the XmlAdapter is as follows:
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> {
@Override
public AdaptedMap marshal(Map 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 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 unmarshal(AdaptedMap adaptedMap) throws Exception {
Map map = new HashMap();
Element rootElement = (Element) adaptedMap.getValue();
NodeList childNodes = rootElement.getChildNodes();
for(int x=0,size=childNodes.getLength(); x
The AdpatedMap class is where all the magic happens, we will use a DOM to represent the content. We will trick JAXB intro dealing with a DOM through the combination of @XmlAnyElement and a property of type Object:
AdpatedMap类是所有神奇的地方,我们将使用DOM来表示内容。我们将通过@XmlAnyElement和类型对象的属性的组合来欺骗JAXB intro处理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;
}
}
This solution requires the MOXy JAXB implementation. You can configure the JAXB runtime to use the MOXy implementation by adding a file named jaxb.properties in with your model classes with the following entry:
这个解决方案需要MOXy JAXB实现。您可以通过添加名为JAXB的文件来配置JAXB运行时以使用MOXy实现。与您的模型类一起的属性,包括以下条目:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
The following demo code can be used to verify the code:
以下演示代码可用于验证代码:
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);
}
}
6
I didn't see anything that really answered this very well. I found something that worked pretty well here:
我没有看到任何能很好地回答这个问题的东西。我发现了一些非常有用的东西:
Use JAXB XMLAnyElement type of style to return dynamic element names
使用JAXB XMLAnyElement样式类型返回动态元素名称
I modified it a bit to support hashmap trees. You could add other collections.
我对它做了一点修改,以支持hashmap树。您可以添加其他集合。
public class MapAdapter extends XmlAdapter> {
@Override
public MapWrapper marshal(Map m) throws Exception {
MapWrapper wrapper = new MapWrapper();
List elements = new ArrayList();
for (Map.Entry property : m.entrySet()) {
if (property.getValue() instanceof Map)
elements.add(new JAXBElement(new QName(getCleanLabel(property.getKey())),
MapWrapper.class, marshal((Map) property.getValue())));
else
elements.add(new JAXBElement(new QName(getCleanLabel(property.getKey())),
String.class, property.getValue().toString()));
}
wrapper.elements = elements;
return wrapper;
}
@Override
public Map 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;
}
Then to implement it:
然后去实现它:
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 childMap;
}
Feeding this through a simple Marshaller gives output that looks like this:
通过一个简单的编组器进行输入,输出如下所示:
Full Name
1234 Main St
value2
value1
childvalue1
3
(Sorry, can't add comments)
(对不起,不能添加评论)
In Blaise's answer above, if you change:
在以上布莱斯的回答中,如果你改变:
@XmlJavaTypeAdapter(MapAdapter.class)
public Map getMapProperty() {
return mapProperty;
}
to:
:
@XmlJavaTypeAdapter(MapAdapter.class)
@XmlPath(".") // <<-- add this
public Map getMapProperty() {
return mapProperty;
}
then this should get rid of the
tag, and so give you:
那么这个应该可以去掉
ALTERNATIVELY:
另外:
You can also change it to:
您也可以将其更改为:
@XmlJavaTypeAdapter(MapAdapter.class)
@XmlAnyElement // <<-- add this
public Map getMapProperty() {
return mapProperty;
}
and then you can get rid of AdaptedMap
altogether, and just change MapAdapter
to marshall to a Document
object directly. I've only tested this with marshalling, so there may be unmarshalling issues.
然后您可以完全摆脱AdaptedMap,直接将MapAdapter更改为marshall文档对象。我只对它进行了编组测试,因此可能存在编组问题。
I'll try and find the time to knock up a full example of this, and edit this post accordingly.
我将尝试找出时间来编写一个完整的示例,并相应地编辑这篇文章。
2
I have solution without adapter. Transient map converted to xml-elements and vise versa:
我有没有适配器的解决方案。瞬态映射转换为xml元素,反之亦然:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "SchemaBasedProperties")
public class SchemaBasedProperties
{
@XmlTransient
Map> properties;
@XmlAnyElement(lax = true)
List xmlmap;
public Map> getProperties()
{
if (properties == null)
properties = new LinkedHashMap>(); // I want same order
return properties;
}
boolean beforeMarshal(Marshaller m)
{
try
{
if (properties != null && !properties.isEmpty())
{
if (xmlmap == null)
xmlmap = new ArrayList();
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 attrs;
for (Map.Entry> it: properties.entrySet())
{
element = doc.createElement(it.getKey());
attrs = it.getValue();
if (attrs != null)
for (Map.Entry 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 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();
for (int i = 0, l = nodeMap.getLength(); i attrs;
attrs = new HashMap();
attrs.put("ResId", "A_LABEL");
props.getProperties().put("LABEL", attrs);
attrs = new HashMap();
attrs.put("ResId", "A_TOOLTIP");
props.getProperties().put("TOOLTIP", attrs);
attrs = new HashMap();
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());
}
}
My output as espected:
我的输出,除了:
{LABEL={ResId=A_LABEL}, TOOLTIP={ResId=A_TOOLTIP}, DISPLAYHINT={Value=hide}}
You can use element name/value pair. I need attributes... Have fun!
可以使用元素名称/值对。我需要属性……玩得开心!
2
Seems like this question is kind of duplicate with another one, where I've collect some marshal/unmarshal solutions into one post. You may check it here: Dynamic tag names with JAXB.
看起来这个问题和另一个问题有点相似,我在一个帖子中收集了一些封送/封送解决方案。您可以在这里检查它:使用JAXB的动态标记名称。
In short:
简而言之:
@xmlAnyElement
should be createdXmlAdapter
can be used in pair with @XmlJavaTypeAdapter
to convert between the container class and Map<>;0
I found easiest solution.
我发现最简单的解决方案。
@XmlElement(name="attribute")
public String[] getAttributes(){
return attributes.keySet().toArray(new String[1]);
}
}
Now it will generate in you xml output like this:
现在,它将在您的xml输出中生成如下内容:
key1
...
keyN
0
When using xml-apis-1.0, you can serialize and deserialize this:
在使用xml-api -1.0时,您可以对其进行序列化和反序列化:
Using this code:
使用这段代码:
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 elements;
public void setFromMap(Map values) {
Document document;
try {
document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
}
for (Entry entry : values.entrySet()) {
Element mapElement = document.createElement(entry.getKey());
mapElement.appendChild(document.createTextNode(entry.getValue()));
elements.add(mapElement);
}
}
public Map getAsMap() {
Map map = new HashMap();
for (Element element : elements) {
if (element.getNodeType() == Node.ELEMENT_NODE) {
map.put(element.getLocalName(), element.getFirstChild().getNodeValue());
}
}
return map;
}
}