Saltar al contenido

Nombres de etiquetas dinámicas con JAXB

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 @XmlAnyElementpropiedad anotada y devuelve los elementos como JAXBElements:

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:

  1. 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.
  2. Un XmlAdapter clase que maneja el ordenamiento / desglose entre Map hacia / desde esta clase de contenedor.
  3. Anote cualquier campo de mapa de su bean java con @XmlJavaTypeAdapter, con este XmlAdapter 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> 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> 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> strFuncs) for(Class clazz : strFuncs.keySet()) if(clazz.isInstance(obj)) return Optional.of(strFuncs.get(clazz).apply(obj)); return Optional.empty();

Notas:

  1. Para JAXB Binding, todo lo que necesita prestar atención es esto
    getProperties método, que se anotan por @XmlAnyElement.
  2. 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 desordenado MapWrapper mediante JAXBContext (en lugar de crearlo usted mismo a través de un new operador).
  3. toMap se introduce aquí para la sonda de información, es decir, ayuda para comprobar las entradas del mapa almacenadas en este MapWrapper 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:


    
        value1
        value2
    
    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> 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> nameExtractors = new HashMap<>(); private static Map, Function> attrExtractors = new HashMap<>(); private static Map, Function> descExtractors = new HashMap<>(); private static Map, Function> 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


   <2019-01-01>Yes
   <2019-02-01>No

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:


   val
   <2019-01-01>Yes

Más adelante puedes encontrar las referencias de otros usuarios, tú además tienes la opción de dejar el tuyo si te gusta.

¡Haz clic para puntuar esta entrada!
(Votos: 2 Promedio: 4.5)


Tags :

Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *