Java 使用 JAXB 的动态标记名称
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3293493/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Dynamic tag names with JAXB
提问by shane
I am using Jersey and JAXB to build a simple RESTful webservice I have a HashMap of 'String' to 'Integer':
我正在使用 Jersey 和 JAXB 来构建一个简单的 RESTful web 服务,我有一个从“String”到“Integer”的 HashMap:
2010-04 -> 24
2010-05 -> 45
I need to generate an XML response which looks like this:
我需要生成一个如下所示的 XML 响应:
<map>
<2010-04>24</2010-04>
<2010-05>45</2010-05>
</map>
What is the best way to generate dynamic tag names with JAXB?
使用 JAXB 生成动态标记名称的最佳方法是什么?
采纳答案by axtavt
You can use an @XmlAnyElement
-annotated property and return the elements as JAXBElement
s:
您可以使用@XmlAnyElement
-annotated 属性并将元素作为JAXBElement
s返回:
private Map<String, Integer> months = ...;
@XmlAnyElement
public List<JAXBElement<Integer>> getMonths() {
List<JAXBElement<Integer>> elements = new ArrayList<JAXBElement<Integer>>();
for (Map.Entry<String, Integer> month: months.entrySet())
elements.add(new JAXBElement(new QName(month.getKey()),
Integer.class, month.getValue()));
return elements;
}
This approach is ugly, but not uglier than the XML it produces.
这种方法很丑陋,但并不比它生成的 XML 更丑陋。
回答by mec_test_1
Also came into this kind of problem recently. After referencing axtavt's answer listed above (and a bunch of other question threads), I made a summary for this kind of problem:
最近也遇到了这种问题。在参考了上面列出的 axtavt 的答案(以及一堆其他问题线程)后,我对此类问题做了一个总结:
- A container class that holds a list (or array) of
JAXBElement
objects, where this list (or array) is annotated with@XmlAnyElement
, thus dynamic element names could be generated. - An
XmlAdapter
class that handles marshalling/unmarshalling between Map to/from this container class. - Annotate any Map fields of your java bean with
@XmlJavaTypeAdapter
, with thisXmlAdapter
class as its value (or you can simply use the container class directly, as you can see below).
- 一个包含
JAXBElement
对象列表(或数组)的容器类,其中该列表(或数组)用 注释@XmlAnyElement
,因此可以生成动态元素名称。 - 一个
XmlAdapter
处理映射到/从这个容器类之间的编组/解组的类。 - 使用 , 注释您的 java bean 的任何 Map 字段
@XmlJavaTypeAdapter
,并将此类XmlAdapter
作为其值(或者您可以直接使用容器类,如下所示)。
Now I'll take Map<String, String>
as an example here, where
现在我将以Map<String, String>
这里为例,其中
{"key1": "value1", "key2": "value2"}
will be marshalled into
将被编组到
<root>
<key1>value1</key1>
<key2>value2</key2>
</root>
Below is the full code snippet & comments, as well as examples:
以下是完整的代码片段和注释,以及示例:
1, The Container (for @XmlAnyElement)
1、容器(对于@XmlAnyElement)
/**
* <dl>
* <dt>References:
* </dt>
* <dd>
* <ul>
* <li><a href="http://stackoverflow.com/questions/21382202/use-jaxb-xmlanyelement-type-of-style-to-return-dynamic-element-names">Dynamic element names in JAXB</a></li>
* <li><a href="http://stackoverflow.com/questions/3941479/jaxb-how-to-marshall-map-into-keyvalue-key">Marshal Map into key-value pairs</a></li>
* <li><a href="http://stackoverflow.com/questions/3293493/dynamic-tag-names-with-jaxb">Dynamic tag names with JAXB</a></li>
* </ul>
* </dd>
* </dl>
* @author MEC
*
*/
@XmlType
public static class MapWrapper{
private List<JAXBElement<String>> properties = new ArrayList<>();
public MapWrapper(){
}
/**
* <p>
* Funny fact: due to type erasure, this method may return
* List<Element> instead of List<JAXBElement<String>> in the end;
* </p>
* <h4>WARNING: do not use this method in your programme</h4>
* <p>
* Thus to retrieve map entries you've stored in this MapWrapper, it's
* recommended to use {@link #toMap()} instead.
* </p>
* @return
*/
@XmlAnyElement
public List<JAXBElement<String>> getProperties() {
return properties;
}
public void setProperties(List<JAXBElement<String>> properties) {
this.properties = properties;
}
/**
* <p>
* Only use {@link #addEntry(JAXBElement)} and {{@link #addEntry(String, String)}
* when this <code>MapWrapper</code> instance is created by yourself
* (instead of through unmarshalling).
* </p>
* @param key map key
* @param value map value
*/
public void addEntry(String key, String value){
JAXBElement<String> prop = new JAXBElement<String>(new QName(key), String.class, value);
addEntry(prop);
}
public void addEntry(JAXBElement<String> prop){
properties.add(prop);
}
@Override
public String toString() {
return "MapWrapper [properties=" + toMap() + "]";
}
/**
* <p>
* To Read-Only Map
* </p>
*
* @return
*/
public Map<String, String> toMap(){
//Note: Due to type erasure, you cannot use properties.stream() directly when unmashalling is used..
List<?> props = properties;
return props.stream().collect(Collectors.toMap(MapWrapper::extractLocalName, MapWrapper::extractTextContent));
}
/**
* <p>
* Extract local name from <code>obj</code>, whether it's javax.xml.bind.JAXBElement or org.w3c.dom.Element;
* </p>
* @param obj
* @return
*/
@SuppressWarnings("unchecked")
private static String extractLocalName(Object obj){
Map<Class<?>, Function<? super Object, String>> strFuncs = new HashMap<>();
strFuncs.put(JAXBElement.class, (jaxb) -> ((JAXBElement<String>)jaxb).getName().getLocalPart());
strFuncs.put(Element.class, ele -> ((Element) ele).getLocalName());
return extractPart(obj, strFuncs).orElse("");
}
/**
* <p>
* Extract text content from <code>obj</code>, whether it's javax.xml.bind.JAXBElement or org.w3c.dom.Element;
* </p>
* @param obj
* @return
*/
@SuppressWarnings("unchecked")
private static String extractTextContent(Object obj){
Map<Class<?>, Function<? super Object, String>> strFuncs = new HashMap<>();
strFuncs.put(JAXBElement.class, (jaxb) -> ((JAXBElement<String>)jaxb).getValue());
strFuncs.put(Element.class, ele -> ((Element) ele).getTextContent());
return extractPart(obj, strFuncs).orElse("");
}
/**
* Check class type of <code>obj</code> according to types listed in <code>strFuncs</code> keys,
* then extract some string part from it according to the extract function specified in <code>strFuncs</code>
* values.
* @param obj
* @param strFuncs
* @return
*/
private static <ObjType, T> Optional<T> extractPart(ObjType obj, Map<Class<?>, Function<? super ObjType, T>> strFuncs){
for(Class<?> clazz : strFuncs.keySet()){
if(clazz.isInstance(obj)){
return Optional.of(strFuncs.get(clazz).apply(obj));
}
}
return Optional.empty();
}
}
Notes:
笔记:
- For the JAXB Binding, all you need to pay attention is this
getProperties
method, which get annotated by@XmlAnyElement
. - Two
addEntry
methods are introduced here for easy of use. They should be used carefully though, as things may turn out horribly wrong when they are used for a freshly unmarshalledMapWrapper
throughJAXBContext
(instead of created by yourself through anew
operator). toMap
is introduced here for info probe, i.e. help to check map entries stored in thisMapWrapper
instance.
- 对于 JAXB Binding,你只需要注意这个
getProperties
方法,它被@XmlAnyElement
. addEntry
这里介绍两种方法,方便使用。但是应该谨慎使用它们,因为当它们用于新鲜解组的MapWrapper
通过JAXBContext
(而不是您自己通过new
操作员创建)时,事情可能会变得非常错误。toMap
这里引入了信息探针,即帮助检查存储在此MapWrapper
实例中的地图条目。
2, The Adapter (XmlAdapter)
2、适配器(XmlAdapter)
XmlAdapter
is used in pair with @XmlJavaTypeAdapter
, which in this case is only needed when Map<String, String>
is used as a bean property.
XmlAdapter
与 成对使用@XmlJavaTypeAdapter
,在这种情况下仅在Map<String, String>
用作 bean 属性时才需要。
/**
* <p>
* ref: http://stackoverflow.com/questions/21382202/use-jaxb-xmlanyelement-type-of-style-to-return-dynamic-element-names
* </p>
* @author MEC
*
*/
public static class MapAdapter extends XmlAdapter<MapWrapper, Map<String, String>>{
@Override
public Map<String, String> unmarshal(MapWrapper v) throws Exception {
Map<String, String> map = v.toMap();
return map;
}
@Override
public MapWrapper marshal(Map<String, String> m) throws Exception {
MapWrapper wrapper = new MapWrapper();
for(Map.Entry<String, String> entry : m.entrySet()){
wrapper.addEntry(new JAXBElement<String>(new QName(entry.getKey()), String.class, entry.getValue()));
}
return wrapper;
}
}
3, Examples
3、实例
Here are two examples showing usage of the container & adapter.
以下是两个示例,显示了容器和适配器的用法。
3.1 Example 1
3.1 例 1
To map this xml:
要映射此 xml:
<root>
<key1>value1</key1>
<key2>value2</key2>
<root>
You can use the following class:
您可以使用以下类:
@XmlRootElement(name="root")
public class CustomMap extends MapWrapper{
public CustomMap(){
}
}
Test Code:
测试代码:
CustomMap map = new CustomMap();
map.addEntry("key1", "value1");
map.addEntry("key1", "value2");
StringWriter sb = new StringWriter();
JAXBContext.newInstance(CustomMap.class).createMarshaller().marshal(map, sb);
out.println(sb.toString());
Note that no @XmlJavaTypeAdapter
is used here.
请注意,@XmlJavaTypeAdapter
这里使用了no 。
3.2 Example 2
3.2 例 2
To map this xml:
要映射此 xml:
<root>
<map>
<key1>value1</key1>
<key2>value2</key2>
</map>
<other>other content</other>
</root>
You can use the following class:
您可以使用以下类:
@XmlRootElement(name="root")
@XmlType(propOrder={"map", "other"})
public class YetAnotherBean{
private Map<String, String> map = new HashMap<>();
private String other;
public YetAnotherBean(){
}
public void putEntry(String key, String value){
map.put(key, value);
}
@XmlElement(name="map")
@XmlJavaTypeAdapter(MapAdapter.class)
public Map<String, String> getMap(){
return map;
}
public void setMap(Map<String, String> map){
this.map = map;
}
@XmlElement(name="other")
public String getOther(){
return other;
}
public void setOther(String other){
this.other = other;
}
}
Test Code:
测试代码:
YetAnotherBean yab = new YetAnotherBean();
yab.putEntry("key1", "value1");
yab.putEntry("key2", "value2");
yab.setOther("other content");
StringWriter sb = new StringWriter();
JAXBContext.newInstance(YetAnotherBean.class).createMarshaller().marshal(yab, sb);
out.println(sb.toString());
Note that @XmlJavaTypeAdapter
is applied onto the Map<String, String>
field with MapAdapter
as its value.
请注意,@XmlJavaTypeAdapter
将其应用于Map<String, String>
字段,MapAdapter
作为其值。
3.3 Example 3
3.3 例 3
Now let's add add some attributes to these elements. Due to some mysterious reasons, I have this kind of XML structure to map:
现在让我们为这些元素添加一些属性。由于一些神秘的原因,我有这种XML结构来映射:
<sys-config>
<sys-params>
<ACCESSLOG_FILE_BY attr="C" desc="AccessLog file desc">SYSTEM</ACCESSLOG_FILE_BY>
<ACCESSLOG_WRITE_MODE attr="D" desc="">DB</ACCESSLOG_WRITE_MODE>
<CHANEG_BUTTON_IMAGES attr="E" desc="Button Image URL, eh, boolean value. ...Wait, what?">FALSE</CHANEG_BUTTON_IMAGES>
</sys-params>
</sys-config>
As you can see, system parameter names are all set to be the element's name instead of as its attribute. To resolve this problem we can use a little help from JAXBElement
again:
如您所见,系统参数名称都设置为元素的名称而不是其属性。为了解决这个问题,我们可以JAXBElement
再次使用一些帮助:
@XmlRootElement(name="sys-config")
public class SysParamConfigXDO{
private SysParamEntries sysParams = new SysParamEntries();
public SysParamConfigXDO(){
}
public void addSysParam(String name, String value, String attr, String desc){
sysParams.addEntry(name, value, attr, desc);;
}
@XmlElement(name="sys-params")
@XmlJavaTypeAdapter(SysParamEntriesAdapter.class)
public SysParamEntries getSysParams() {
return sysParams;
}
public void setSysParams(SysParamEntries sysParams) {
this.sysParams = sysParams;
}
@Override
public String toString() {
return "SysParamConfigXDO [sysParams=" + sysParams + "]";
}
}
@XmlRootElement(name="root")
public class SysParamXDO extends SysParamEntriesWrapper{
public SysParamXDO(){
}
}
@SuppressWarnings("unchecked")
@XmlType
public class SysParamEntriesWrapper{
/**
* <p>
* Here is the tricky part:
* <ul>
* <li>When this <code>SysParamEntriesWrapper</code> is created by yourself, objects
* stored in this <code>entries</code> list is of type SystemParamEntry</li>
* <li>Yet during the unmarshalling process, this <code>SysParamEntriesWrapper</code> is
* created by the JAXBContext, thus objects stored in the <code>entries</code> is
* of type Element actually.</li>
* </ul>
* </p>
*/
List<JAXBElement<SysParamEntry>> entries = new ArrayList<>();
public SysParamEntriesWrapper(){
}
public void addEntry(String name, String value, String attr, String desc){
addEntry(new SysParamEntry(name, value, attr, desc));
}
public void addEntry(String name, String value){
addEntry(new SysParamEntry(name, value));
}
public void addEntry(SysParamEntry entry){
JAXBElement<SysParamEntry> bean = new JAXBElement<SysParamEntry>(new QName("", entry.getName()), SysParamEntry.class, entry);
entries.add(bean);
}
@XmlAnyElement
public List<JAXBElement<SysParamEntry>> getEntries() {
return entries;
}
public void setEntries(List<JAXBElement<SysParamEntry>> entries) {
this.entries = entries;
}
@Override
public String toString() {
return "SysParammEntriesWrapper [entries=" + toMap() + "]";
}
public Map<String, SysParamEntry> toMap(){
Map<String, SysParamEntry> retval = new HashMap<>();
List<?> entries = this.entries;
entries.stream().map(SysParamEntriesWrapper::convertToParamEntry).
forEach(entry -> retval.put(entry.getName(), entry));;
return retval;
}
private static SysParamEntry convertToParamEntry(Object entry){
String name = extractName(entry);
String attr = extractAttr(entry);
String desc = extractDesc(entry);
String value = extractValue(entry);
return new SysParamEntry(name, value, attr, desc);
}
@SuppressWarnings("unchecked")
private static String extractName(Object entry){
return extractPart(entry, nameExtractors).orElse("");
}
@SuppressWarnings("unchecked")
private static String extractAttr(Object entry){
return extractPart(entry, attrExtractors).orElse("");
}
@SuppressWarnings("unchecked")
private static String extractDesc(Object entry){
return extractPart(entry, descExtractors).orElse("");
}
@SuppressWarnings("unchecked")
private static String extractValue(Object entry){
return extractPart(entry, valueExtractors).orElse("");
}
private static <ObjType, RetType> Optional<RetType> extractPart(ObjType obj, Map<Class<?>,
Function<? super ObjType, RetType>> extractFuncs ){
for(Class<?> clazz : extractFuncs.keySet()){
if(clazz.isInstance(obj)){
return Optional.ofNullable(extractFuncs.get(clazz).apply(obj));
}
}
return Optional.empty();
}
private static Map<Class<?>, Function<? super Object, String>> nameExtractors = new HashMap<>();
private static Map<Class<?>, Function<? super Object, String>> attrExtractors = new HashMap<>();
private static Map<Class<?>, Function<? super Object, String>> descExtractors = new HashMap<>();
private static Map<Class<?>, Function<? super Object, String>> valueExtractors = new HashMap<>();
static{
nameExtractors.put(JAXBElement.class, jaxb -> ((JAXBElement<SysParamEntry>)jaxb).getName().getLocalPart());
nameExtractors.put(Element.class, ele -> ((Element) ele).getLocalName());
attrExtractors.put(JAXBElement.class, jaxb -> ((JAXBElement<SysParamEntry>)jaxb).getValue().getAttr());
attrExtractors.put(Element.class, ele -> ((Element) ele).getAttribute("attr"));
descExtractors.put(JAXBElement.class, jaxb -> ((JAXBElement<SysParamEntry>)jaxb).getValue().getDesc());
descExtractors.put(Element.class, ele -> ((Element) ele).getAttribute("desc"));
valueExtractors.put(JAXBElement.class, jaxb -> ((JAXBElement<SysParamEntry>)jaxb).getValue().getValue());
valueExtractors.put(Element.class, ele -> ((Element) ele).getTextContent());
}
}
public class SysParamEntriesAdapter extends XmlAdapter<SysParamEntriesWrapper, SysParamEntries>{
@Override
public SysParamEntries unmarshal(SysParamEntriesWrapper v) throws Exception {
SysParamEntries retval = new SysParamEntries();
v.toMap().values().stream().forEach(retval::addEntry);
return retval;
}
@Override
public SysParamEntriesWrapper marshal(SysParamEntries v) throws Exception {
SysParamEntriesWrapper entriesWrapper = new SysParamEntriesWrapper();
v.getEntries().forEach(entriesWrapper::addEntry);
return entriesWrapper;
}
}
public class SysParamEntries{
List<SysParamEntry> entries = new ArrayList<>();;
public SysParamEntries(){
}
public SysParamEntries(List<SysParamEntry> entries) {
super();
this.entries = entries;
}
public void addEntry(SysParamEntry entry){
entries.add(entry);
}
public void addEntry(String name, String value){
addEntry(name, value, "C");
}
public void addEntry(String name, String value, String attr){
addEntry(name, value, attr, "");
}
public void addEntry(String name, String value, String attr, String desc){
entries.add(new SysParamEntry(name, value, attr, desc));
}
public List<SysParamEntry> getEntries() {
return entries;
}
public void setEntries(List<SysParamEntry> entries) {
this.entries = entries;
}
@Override
public String toString() {
return "SystemParamEntries [entries=" + entries + "]";
}
}
@XmlType
public class SysParamEntry{
String name;
String value = "";
String attr = "";
String desc = "";
public SysParamEntry(){
}
public SysParamEntry(String name, String value) {
super();
this.name = name;
this.value = value;
}
public SysParamEntry(String name, String value, String attr) {
super();
this.name = name;
this.value = value;
this.attr = attr;
}
public SysParamEntry(String name, String value, String attr, String desc) {
super();
this.name = name;
this.value = value;
this.attr = attr;
this.desc = desc;
}
@XmlTransient
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@XmlValue
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@XmlAttribute(name="attr")
public String getAttr() {
return attr;
}
public void setAttr(String attr) {
this.attr = attr;
}
@XmlAttribute(name="desc")
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "SystemParamEntry [name=" + name + ", value=" + value + ", attr=" + attr + ", desc=" + desc + "]";
}
}
And it's time for test:
是时候进行测试了:
//Marshal
SysParamConfigXDO xdo = new SysParamConfigXDO();
xdo.addSysParam("ACCESSLOG_FILE_BY", "SYSTEM", "C", "AccessLog file desc");
xdo.addSysParam("ACCESSLOG_WRITE_MODE", "DB", "D", "");
xdo.addSysParam("CHANEG_BUTTON_IMAGES", "FALSE", "E", "Button Image URL, eh, boolean value. ...Wait, what?");
JAXBContext jaxbCtx = JAXBContext.newInstance(SysParamConfigXDO.class, SysParamEntries.class);
jaxbCtx.createMarshaller().marshal(xdo, System.out);
//Unmarshal
Path xmlFile = Paths.get("path_to_the_saved_xml_file.xml");
JAXBContext jaxbCtx = JAXBContext.newInstance(SysParamConfigXDO.class, SysParamEntries.class);
SysParamConfigXDO xdo = (SysParamConfigXDO) jaxbCtx.createUnmarshaller().unmarshal(xmlFile.toFile());
out.println(xdo.toString());
回答by beendr
Maybe somebody is interested in an easier solution with marshall and unmarshall example.
It is not a map but still a key-value solution because we are using JAXBElement
with a key (=localname) and value (=textcontent).
也许有人对使用 marshall 和 unmarshall 示例的更简单的解决方案感兴趣。它不是地图,但仍然是键值解决方案,因为我们使用JAXBElement
的是键 (=localname) 和值 (=textcontent)。
@XmlRootElement(name="map")
@XmlAccessorType(XmlAccessType.FIELD)
public class XmlMap {
//one caveat (as mec_test_1 pointed out) unmarshalled objects are from type org.w3c.dom.Element and during marshall it is JAXBElement
@XmlAnyElement
List<JAXBElement<String>> dates = new ArrayList<>();
To unmarshall lets say this xml file
解组让我们说这个 xml 文件
<map>
<2019-01-01>Yes</2019-01-01>
<2019-02-01>No</2019-02-01>
</map>
You have to run:
你必须运行:
JAXBContext c = JAXBContext.newInstance(XmlMap.class);
XmlMap map = c.createUnmarshaller().unmarshall(new File("xmlfile.xml"));
//access the objects via
System.out.println("Key: " + ((org.w3c.dom.Element) map.dates.get(0)).getLocalName());
System.out.println("Value: " + ((org.w3c.dom.Element) map.dates.get(0)).getTextContent());
An to marshall a object:
用于编组对象:
import javax.xml.namespace.QName;
import javax.xml.bind.JAXBElement;
XmlMap xmlMap = new XmlMap();
xmlMap.dates.add(new JAXBElement<String>(new QName("key"), String.class, "value"));
xmlMap.dates.add(new JAXBElement<String>(new QName("2019-01-01"), String.class, "Yes"));
JAXBContext context = JAXBContext.newInstance(XmlMap.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.marshal(verzObj, System.out);
Output:
输出:
<map>
<key>val</key>
<2019-01-01>Yes</2019-01-01>
</map>