unmarshal parse online jaxbunmarshaller jaxbcontext example bean java xml jaxb jaxb2

java - parse - JAXB: cómo mapea el mapa en el<valor> clave</ key>



parse xml java jaxb (9)

(Lo siento, no puedo agregar comentarios)

En la respuesta de Blaise anterior, si cambias:

@XmlJavaTypeAdapter(MapAdapter.class) public Map<String, String> getMapProperty() { return mapProperty; }

a:

@XmlJavaTypeAdapter(MapAdapter.class) @XmlPath(".") // <<-- add this public Map<String, String> getMapProperty() { return mapProperty; }

entonces esto debería deshacerse de la etiqueta <mapProperty> , y así darle:

<?xml version="1.0" encoding="UTF-8"?> <root> <map> <key>value</key> <key2>value2</key2> </map> </root>

ALTERNATIVAMENTE:

También puedes cambiarlo a:

@XmlJavaTypeAdapter(MapAdapter.class) @XmlAnyElement // <<-- add this public Map<String, String> getMapProperty() { return mapProperty; }

y luego puede deshacerse de AdaptedMap completo, y simplemente cambiar MapAdapter a Marshall a un objeto de Document directamente. Solo he probado esto con clasificación, por lo que puede haber problemas de falta de coordinación.

Trataré de encontrar el momento para mostrar un ejemplo completo de esto, y editar esta publicación en consecuencia.

La pregunta es acerca de la clasificación de JAXB Map: hay muchos ejemplos sobre cómo margar un mapa en una estructura de la siguiente manera:

<map> <entry> <key> KEY </key> <value> VALUE </value> </entry> <entry> <key> KEY2 </key> <value> VALUE2 </value> </entry> <entry> ... </map>

De hecho, esto es soportado de forma nativa por JAXB. Lo que necesito, sin embargo, es el XML donde la clave es el nombre del elemento, y el valor es su contenido:

<map> <key> VALUE </key> <key2> VALUE2 </key2> ... </map>

No logré implementar mi adaptador Map de la manera en que lo recomiendan los desarrolladores de JAXB ( https://jaxb.dev.java.net/guide/Mapping_your_favorite_class.html ), ya que lo necesito, he - dynamic attribute name :)

¿Hay alguna solución para eso?

PD Actualmente tengo que crear una clase de contenedor dedicada para cada conjunto típico de pares clave-valor que quiero asignar a XML: funciona, pero tengo que crear demasiados contenedores de ayuda.


Al usar xml-apis-1.0, puede serializar y deserializar esto:

<?xml version="1.0" encoding="UTF-8"?> <root> <map> <key>value</key> <key2>value2</key2> </map> </root>

Usando este código:

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; } }


Encontré la solución más fácil.

@XmlElement(name="attribute") public String[] getAttributes(){ return attributes.keySet().toArray(new String[1]); } }

Ahora generará en ti xml de salida de esta manera:

<attribute>key1<attribute> ... <attribute>keyN<attribute>


No vi nada que realmente respondiera esto muy bien. Encontré algo que funcionó bastante bien aquí:

Utilice el tipo de estilo JAXB XMLAnyElement para devolver nombres de elementos dinámicos

Lo modifiqué un poco para soportar árboles hashmap. Puedes agregar otras colecciones.

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; }

Entonces para implementarlo:

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; }

Al alimentar esto a través de un simple Marshaller se obtiene un resultado que se ve así:

<?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>


Parece que esta pregunta es una especie de duplicación con otra, en la que recopilé algunas soluciones de Mariscal / Unmarshal en una publicación. Puede consultarlo aquí: nombres de etiquetas dinámicas con JAXB .

En breve:

  1. Se debe crear una clase contenedor para @xmlAnyElement
  2. Un XmlAdapter se puede usar en conjunto con @XmlJavaTypeAdapter para convertir entre la clase contenedor y Map <>;

Puede haber una razón válida por la que desea hacer esto, pero generalmente es mejor evitar generar este tipo de XML. ¿Por qué? Porque significa que los elementos XML de su mapa dependen del contenido del tiempo de ejecución de su mapa. Y dado que XML se usa generalmente como una interfaz externa o capa de interfaz, esto no es deseable. Dejame explicar.

El esquema Xml (xsd) define el contrato de interfaz de sus documentos XML. Además de poder generar código desde el XSD, JAXB también puede generar el esquema XML para usted desde el código. Esto le permite restringir los datos intercambiados a través de la interfaz a las estructuras acordadas previamente definidas en el XSD.

En el caso predeterminado para un Map<String, String> , el XSD generado restringirá el elemento del mapa para que contenga múltiples elementos de entrada, cada uno de los cuales debe contener una clave xs:string y un valor xs:string . Es un contrato de interfaz bastante claro.

Lo que describes es que quieres que el mapa xml contenga elementos cuyo nombre estará determinado por el contenido del mapa en tiempo de ejecución. Entonces, el XSD generado solo puede especificar que el mapa debe contener una lista de elementos cuyo tipo es desconocido en tiempo de compilación. Esto es algo que generalmente debe evitar al definir un contrato de interfaz.

Para lograr un contrato estricto en este caso, debe usar un tipo enumerado como la clave del mapa en lugar de una cadena. P.ej

public enum KeyType { KEY, KEY2; } @XmlJavaTypeAdapter(MapAdapter.class) Map<KeyType , String> mapProperty;

De esta forma, las claves que desea convertir en elementos en XML se conocen en tiempo de compilación, por lo que JAXB debería poder generar un esquema que restrinja los elementos del mapa a los elementos utilizando una de las teclas predefinidas KEY o KEY2.

Por otro lado, si desea simplificar la estructura generada por defecto

<map> <entry> <key>KEY</key> <value>VALUE</value> </entry> <entry> <key>KEY2</key> <value>VALUE2</value> </entry> </map>

Para algo más simple como este

<map> <item key="KEY" value="VALUE"/> <item key="KEY2" value="VALUE2"/> </map>

Puede usar un MapAdapter que convierta el Map a una matriz de MapElements de la siguiente manera:

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; } }


Tengo una solución sin adaptador. Mapa transitorio convertido a elementos xml y viceversa:

@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()); } }

Mi salida como esperada:

<SchemaBasedProperties> <LABEL ResId="A_LABEL"/> <TOOLTIP ResId="A_TOOLTIP"/> <DISPLAYHINT Value="hide"/> </SchemaBasedProperties> {LABEL={ResId=A_LABEL}, TOOLTIP={ResId=A_TOOLTIP}, DISPLAYHINT={Value=hide}}

Puede usar el par nombre nombre / valor. Necesito atributos ... ¡Diviertete!


Todavía estoy trabajando en una solución mejor, pero con MOXy JAXB , he podido manejar el siguiente XML:

<?xml version="1.0" encoding="UTF-8"?> <root> <mapProperty> <map> <key>value</key> <key2>value2</key2> </map> </mapProperty> </root>

Necesita utilizar un @XmlJavaTypeAdapter en su propiedad Map:

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; } }

La implementación de XmlAdapter es la siguiente:

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; } }

La clase AdpatedMap es donde ocurre toda la magia, usaremos un DOM para representar el contenido. Vamos a engañar a la introducción de JAXB con un DOM a través de la combinación de @XmlAnyElement y una propiedad de tipo Object:

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; } }

Esta solución requiere la implementación MOXy JAXB. Puede configurar el tiempo de ejecución de JAXB para usar la implementación de MOXy agregando un archivo llamado jaxb.properties con sus clases de modelo con la siguiente entrada:

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

El siguiente código de demostración se puede usar para verificar el código:

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); } }


el código proporcionado no funcionó para mí. Encontré otra manera de mapear:

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; } }

El 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; } }

Encontré el código en este sitio web: http://www.developpez.net/forums/d972324/java/general-java/xml/hashmap-jaxb/