JAXB: comment marshaller dans la valeur

La question concerne le regroupement de cartes JAXB – il existe de nombreux exemples sur la façon de regrouper une carte dans une structure comme suit:

   KEY   VALUE     KEY2   VALUE2    ...  

En fait, cela est supporté en natif par JAXB. Ce dont j’ai besoin, cependant, c’est que le code XML où key est le nom de l’élément et que value soit son contenu:

   VALUE   VALUE2  ...  

Je n’ai pas réussi à implémenter mon adaptateur Map comme il est recommandé par les développeurs JAXB ( https://jaxb.dev.java.net/guide/Mapping_your_favorite_class.html ), selon mes besoins, il – nom d’atsortingbut dynamic 🙂

Y a-t-il une solution pour ça?

PS Actuellement, je dois créer une classe de conteneur dédiée pour chaque ensemble typique de paires clé-valeur que je veux regrouper en XML – cela fonctionne, mais je dois créer trop de ces conteneurs d’aide.

Le code fourni ne fonctionnait pas pour moi. J’ai trouvé un autre moyen de mapper:

MapElements:

 package com.cellfish.mediadb.rest.lucene; import javax.xml.bind.annotation.XmlElement; class MapElements { @XmlElement public Ssortingng key; @XmlElement public Integer value; private MapElements() {} //Required by JAXB public MapElements(Ssortingng 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> { 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; } } 

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

J’ai trouvé le code sur ce site: http://www.developpez.net/forums/d972324/java/general-java/xml/hashmap-jaxb/

Il peut y avoir une raison valable pour laquelle vous voulez faire cela, mais il est généralement préférable d’éviter de générer ce type de XML. Pourquoi? Parce que cela signifie que les éléments XML de votre map dépendent du contenu d’exécution de votre map. Et comme XML est généralement utilisé comme interface externe ou couche d’interface, cela n’est pas souhaitable. Laisse-moi expliquer.

Le schéma XML (xsd) définit le contrat d’interface de vos documents XML. En plus de pouvoir générer du code à partir du XSD, JAXB peut également générer le schéma XML à partir du code. Cela vous permet de restreindre les données échangées via l’interface aux structures prédéfinies définies dans le XSD.

Dans le cas par défaut pour une Map , le fichier XSD généré limitera l’élément map à contenir plusieurs éléments d’entrée dont chacun doit contenir une clé xs:ssortingng et une valeur xs:ssortingng . C’est un contrat d’interface assez clair.

Ce que vous décrivez est que vous souhaitez que la carte XML contienne des éléments dont le nom sera déterminé par le contenu de la carte lors de l’exécution. Ensuite, le XSD généré peut uniquement spécifier que la carte doit contenir une liste d’éléments dont le type est inconnu au moment de la compilation. C’est quelque chose que vous devez généralement éviter lors de la définition d’un contrat d’interface.

Pour obtenir un contrat ssortingct dans ce cas, vous devez utiliser un type énuméré comme clé de la carte au lieu d’une chaîne. Par exemple

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

De cette façon, les clés que vous voulez devenir des éléments en XML sont connues au moment de la compilation, donc JAXB devrait être capable de générer un schéma qui limiterait les éléments de la carte aux éléments en utilisant l’une des clés prédéfinies KEY ou KEY2.

Par contre, si vous souhaitez simplifier la structure générée par défaut

   KEY VALUE   KEY2 VALUE2   

Pour quelque chose de plus simple

     

Vous pouvez utiliser un MapAdapter qui convertit la carte en un tableau de MapElements comme suit:

 class MapElements { @XmlAtsortingbute public Ssortingng key; @XmlAtsortingbute public Ssortingng value; private MapElements() { } //Required by JAXB public MapElements(Ssortingng key, Ssortingng 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; } } 

Je travaille toujours sur une meilleure solution mais en utilisant MOXy JAXB , j’ai été capable de gérer le XML suivant:

     value value2    

Vous devez utiliser un @XmlJavaTypeAdapter sur votre propriété 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 mapProperty; public Root() { mapProperty = new HashMap(); } @XmlJavaTypeAdapter(MapAdapter.class) public Map getMapProperty() { return mapProperty; } public void setMapProperty(Map map) { this.mapProperty = map; } } 

L’implémentation de XmlAdapter est la suivante:

 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 

La classe AdpatedMap est l'endroit où toute la magie se produit, nous utiliserons un DOM pour représenter le contenu. Nous allons tromper l'intro de JAXB sur un DOM via la combinaison de @XmlAnyElement et d'une propriété de type 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; } } 

Cette solution nécessite l'implémentation du MOXy JAXB. Vous pouvez configurer le moteur d'exécution JAXB pour utiliser l'implémentation MOXy en ajoutant un fichier nommé jaxb.properties avec vos classes de modèle avec l'entrée suivante:

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

Le code de démonstration suivant peut être utilisé pour vérifier le 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(Ssortingng[] 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); } } 

Je n’ai rien vu qui ait vraiment répondu à cela. J’ai trouvé quelque chose qui fonctionnait plutôt bien ici:

Utilisez le type de style JAXB XMLAnyElement pour renvoyer des noms d’éléments dynamics

Je l’ai un peu modifié pour supporter les arbres hashmap. Vous pouvez append d’autres collections.

 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())), Ssortingng.class, property.getValue().toSsortingng())); } wrapper.elements = elements; return wrapper; } @Override public Map unmarshal(MapWrapper v) throws Exception { // TODO throw new OperationNotSupportedException(); } // Return a XML-safe atsortingbute. Might want to add camel case support private Ssortingng getCleanLabel(Ssortingng atsortingbuteLabel) { atsortingbuteLabel = atsortingbuteLabel.replaceAll("[()]", "").replaceAll("[^\\w\\s]", "_").replaceAll(" ", "_"); return atsortingbuteLabel; } } class MapWrapper { @XmlAnyElement List elements; } 

Ensuite, pour le mettre en œuvre:

 static class myxml { Ssortingng name = "Full Name"; Ssortingng 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; } 

L’alimentation à travers un simple Marshaller donne une sortie qui ressemble à ceci:

   Full Name 
1234 Main St
value2 value1 childvalue1

(Désolé, impossible d’append des commentaires)

Dans la réponse de Blaise ci-dessus, si vous changez:

 @XmlJavaTypeAdapter(MapAdapter.class) public Map getMapProperty() { return mapProperty; } 

à:

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

alors cela devrait se débarrasser de la , et ainsi vous donner:

    value value2   

ALTERNATIVEMENT:

Vous pouvez également le changer pour:

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

et puis vous pouvez vous débarrasser complètement d’ AdaptedMap , et il suffit de changer MapAdapter en marshall directement à un object Document . Je n’ai fait que tester cela avec le regroupement, donc il peut y avoir des problèmes de démolition.

Je vais essayer de trouver le temps de faire un exemple complet et de modifier ce post en conséquence.

J’ai une solution sans adaptateur. Carte transitoire convertie en éléments xml et vice versa:

 @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.setAtsortingbute(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; Ssortingng 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.getAtsortingbutes(); name = node.getLocalName(); attrs = new HashMap(); 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 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()); } } 

Mon résultat est le suivant:

      {LABEL={ResId=A_LABEL}, TOOLTIP={ResId=A_TOOLTIP}, DISPLAYHINT={Value=hide}} 

Vous pouvez utiliser une paire nom / valeur d’élément. J’ai besoin d’atsortingbuts … Amusez-vous!

On dirait que cette question est en quelque sorte en double avec une autre, où je collectionne des solutions de marshal / unmarshal dans un seul post. Vous pouvez le vérifier ici: Noms de balises dynamics avec JAXB .

En bref:

  1. Une classe de conteneur pour @xmlAnyElement doit être créée
  2. Un XmlAdapter peut être utilisé en paire avec @XmlJavaTypeAdapter pour convertir entre la classe de conteneur et Map <>;

J’ai trouvé la solution la plus simple.

 @XmlElement(name="atsortingbute") public Ssortingng[] getAtsortingbutes(){ return atsortingbutes.keySet().toArray(new Ssortingng[1]); } } 

Maintenant, il générera dans votre sortie xml comme ceci:

 key1 ... keyN 

Lorsque vous utilisez xml-apis-1.0, vous pouvez le sérialiser et le désérialiser:

    value value2   

En utilisant ce 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(Ssortingng[] 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; } }