Luego de de una larga recopilación de información dimos con la respuesta este disgusto que pueden tener ciertos usuarios. Te brindamos la respuesta y esperamos servirte de mucha ayuda.
Puedes usar un @XmlAnyElement
propiedad anotada y devuelve los elementos como JAXBElement
s:
private Map months = ...;
@XmlAnyElement
public List> getMonths()
List> elements = new ArrayList>();
for (Map.Entry month: months.entrySet())
elements.add(new JAXBElement(new QName(month.getKey()),
Integer.class, month.getValue()));
return elements;
Este enfoque es feo, pero no más feo que el XML que produce.
También entró en este tipo de problema recientemente. Después de hacer referencia a la respuesta de axtavt enumerada anteriormente (y un montón de otros hilos de preguntas), hice un resumen para este tipo de problema:
- Una clase de contenedor que contiene una lista (o array) de
JAXBElement
objetos, donde esta lista (o array) está anotado con
@XmlAnyElement
, por lo que se podrían generar nombres de elementos dinámicos. - Un
XmlAdapter
clase que maneja el ordenamiento / desglose entre Map hacia / desde esta clase de contenedor. - Anote cualquier campo de mapa de su bean java con
@XmlJavaTypeAdapter
, con esteXmlAdapter
class como su valor (o simplemente puede usar la clase contenedora directamente, como puede ver a continuación).
Ahora tomaré Map
como ejemplo aquí, donde
"key1": "value1", "key2": "value2"
será reunido en
value1
value2
A continuación se muestran los comentarios y el fragmento de código completo, así como ejemplos:
1, el contenedor (para @XmlAnyElement)
/**
*
* - References:
*
* -
*
*
*
* @author MEC
*
*/
@XmlType
public static class MapWrapper{
private List> properties = new ArrayList<>();
public MapWrapper()
/**
*
* Funny fact: due to type erasure, this method may return
* List instead of List> in the end;
*
* WARNING: do not use this method in your programme
*
* Thus to retrieve map entries you've stored in this MapWrapper, it's
* recommended to use @link #toMap() instead.
*
* @return
*/
@XmlAnyElement
public List> getProperties()
return properties;
public void setProperties(List> properties)
this.properties = properties;
/**
*
* Only use @link #addEntry(JAXBElement) and @link #addEntry(String, String)
* when this MapWrapper
instance is created by yourself
* (instead of through unmarshalling).
*
* @param key map key
* @param value map value
*/
public void addEntry(String key, String value)
JAXBElement prop = new JAXBElement(new QName(key), String.class, value);
addEntry(prop);
public void addEntry(JAXBElement prop)
properties.add(prop);
@Override
public String toString()
return "MapWrapper [properties=" + toMap() + "]";
/**
*
* To Read-Only Map
*
*
* @return
*/
public Map 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));
/**
*
* Extract local name from obj
, whether it's javax.xml.bind.JAXBElement or org.w3c.dom.Element;
*
* @param obj
* @return
*/
@SuppressWarnings("unchecked")
private static String extractLocalName(Object obj)
Map, Function super Object, String>> strFuncs = new HashMap<>();
strFuncs.put(JAXBElement.class, (jaxb) -> ((JAXBElement)jaxb).getName().getLocalPart());
strFuncs.put(Element.class, ele -> ((Element) ele).getLocalName());
return extractPart(obj, strFuncs).orElse("");
/**
*
* Extract text content from obj
, whether it's javax.xml.bind.JAXBElement or org.w3c.dom.Element;
*
* @param obj
* @return
*/
@SuppressWarnings("unchecked")
private static String extractTextContent(Object obj)
Map, Function super Object, String>> strFuncs = new HashMap<>();
strFuncs.put(JAXBElement.class, (jaxb) -> ((JAXBElement)jaxb).getValue());
strFuncs.put(Element.class, ele -> ((Element) ele).getTextContent());
return extractPart(obj, strFuncs).orElse("");
/**
* Check class type of obj
according to types listed in strFuncs
keys,
* then extract some string part from it according to the extract function specified in strFuncs
* values.
* @param obj
* @param strFuncs
* @return
*/
private static Optional extractPart(ObjType obj, Map, 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();
Notas:
- Para JAXB Binding, todo lo que necesita prestar atención es esto
getProperties
método, que se anotan por@XmlAnyElement
. - Dos
addEntry
Los métodos se presentan aquí para facilitar su uso. Sin embargo, deben usarse con cuidado, ya que las cosas pueden salir terriblemente mal cuando se usan para un recién desordenadoMapWrapper
medianteJAXBContext
(en lugar de crearlo usted mismo a través de unnew
operador). toMap
se introduce aquí para la sonda de información, es decir, ayuda para comprobar las entradas del mapa almacenadas en esteMapWrapper
ejemplo.
2, el adaptador (XmlAdapter)
XmlAdapter
se usa en pareja con @XmlJavaTypeAdapter
, que en este caso solo es necesario cuando Map
se utiliza como una propiedad de frijol.
/**
*
* ref: http://stackoverflow.com/questions/21382202/use-jaxb-xmlanyelement-type-of-style-to-return-dynamic-element-names
*
* @author MEC
*
*/
public static class MapAdapter extends XmlAdapter>
@Override
public Map unmarshal(MapWrapper v) throws Exception
Map map = v.toMap();
return map;
@Override
public MapWrapper marshal(Map m) throws Exception
MapWrapper wrapper = new MapWrapper();
for(Map.Entry entry : m.entrySet())
wrapper.addEntry(new JAXBElement(new QName(entry.getKey()), String.class, entry.getValue()));
return wrapper;
3, ejemplos
Aquí hay dos ejemplos que muestran el uso del contenedor y el adaptador.
3.1 Ejemplo 1
Para mapear este xml:
value1
value2
Puede utilizar la siguiente clase:
@XmlRootElement(name="root")
public class CustomMap extends MapWrapper
public CustomMap()
Código de prueba:
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());
Tenga en cuenta que no @XmlJavaTypeAdapter
se utiliza aquí.
3.2 Ejemplo 2
Para mapear este xml:
other content
Puede utilizar la siguiente clase:
@XmlRootElement(name="root")
@XmlType(propOrder="map", "other")
public class YetAnotherBean
private Map 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 getMap()
return map;
public void setMap(Map map)
this.map = map;
@XmlElement(name="other")
public String getOther()
return other;
public void setOther(String other)
this.other = other;
Código de prueba:
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());
Tenga en cuenta que @XmlJavaTypeAdapter
se aplica sobre el Map
campo con MapAdapter
como su valor.
3.3 Ejemplo 3
Ahora agreguemos, agreguemos algunos attributes a estos elementos. Debido a algunas razones misteriosas, tengo este tipo de estructura XML para mapear:
SYSTEM
DB
FALSE
Como puede ver, los nombres de los parámetros del sistema están configurados para ser el nombre del elemento en lugar de como su attribute. Para resolver este problema, podemos utilizar un poco de ayuda de JAXBElement
de nuevo:
@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
/**
*
* Here is the tricky part:
*
* - When this
SysParamEntriesWrapper
is created by yourself, objects
* stored in this entries
list is of type SystemParamEntry
* - Yet during the unmarshalling process, this
SysParamEntriesWrapper
is
* created by the JAXBContext, thus objects stored in the entries
is
* of type Element actually.
*
*
*/
List> 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 bean = new JAXBElement(new QName("", entry.getName()), SysParamEntry.class, entry);
entries.add(bean);
@XmlAnyElement
public List> getEntries()
return entries;
public void setEntries(List> entries)
this.entries = entries;
@Override
public String toString()
return "SysParammEntriesWrapper [entries=" + toMap() + "]";
public Map toMap()
Map 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 Optional extractPart(ObjType obj, Map,
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, Function super Object, String>> nameExtractors = new HashMap<>();
private static Map, Function super Object, String>> attrExtractors = new HashMap<>();
private static Map, Function super Object, String>> descExtractors = new HashMap<>();
private static Map, Function super Object, String>> valueExtractors = new HashMap<>();
static
nameExtractors.put(JAXBElement.class, jaxb -> ((JAXBElement)jaxb).getName().getLocalPart());
nameExtractors.put(Element.class, ele -> ((Element) ele).getLocalName());
attrExtractors.put(JAXBElement.class, jaxb -> ((JAXBElement)jaxb).getValue().getAttr());
attrExtractors.put(Element.class, ele -> ((Element) ele).getAttribute("attr"));
descExtractors.put(JAXBElement.class, jaxb -> ((JAXBElement)jaxb).getValue().getDesc());
descExtractors.put(Element.class, ele -> ((Element) ele).getAttribute("desc"));
valueExtractors.put(JAXBElement.class, jaxb -> ((JAXBElement)jaxb).getValue().getValue());
valueExtractors.put(Element.class, ele -> ((Element) ele).getTextContent());
public class SysParamEntriesAdapter extends XmlAdapter
@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 entries = new ArrayList<>();;
public SysParamEntries()
public SysParamEntries(List 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 getEntries()
return entries;
public void setEntries(List 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 + "]";
Y es hora de probar:
//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());
Quizás alguien esté interesado en una solución más sencilla con el ejemplo de Marshall y Unmarshall. No es un mapa, pero sigue siendo un key-valor solución porque estamos usando JAXBElement
con un key (= nombre local) y valor (= contenido de texto).
@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> dates = new ArrayList<>();
Para unmarshall, digamos este archivo xml
Tienes que correr:
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 para ordenar un objeto:
import javax.xml.namespace.QName;
import javax.xml.bind.JAXBElement;
XmlMap xmlMap = new XmlMap();
xmlMap.dates.add(new JAXBElement(new QName("key"), String.class, "value"));
xmlMap.dates.add(new JAXBElement(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);
Producción:
Más adelante puedes encontrar las referencias de otros usuarios, tú además tienes la opción de dejar el tuyo si te gusta.