66 votes

JAXB: comment marshall carte en <key>valeur</key>

La question est à propos de JAXB Carte de triage - il y a beaucoup d'exemples sur la façon de marhsall une Carte, dans une structure comme suit:

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

En fait, c'est pris en charge nativement par JAXB. Ce dont j'ai besoin, cependant, est le XML où la clé est le nom de l'élément, de la valeur et de son contenu:

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

Je n'ai pas réussir la mise en œuvre de ma Carte d'adaptateur de la façon dont elle est recommandée par JAXB développeurs (https://jaxb.dev.java.net/guide/Mapping_your_favorite_class.html), que j'ai besoin, il - dynamique le nom de l'attribut :)

Est qu'il ya une solution pour cela?

P. S. Actuellement, j'ai créer une classe de conteneur pour chaque typiques ensemble de paires clé-valeur, je veux marshall en XML, il fonctionne, mais je dois créer beaucoup trop de ces helper conteneurs.

Merci pour toutes les idées! Je suis coincé avec ça...

28voto

Justin Rowe Points 506

Il y a peut être une raison valable pourquoi vous voulez le faire, mais la génération de ce type de XML est généralement à éviter. Pourquoi? Car cela signifie que les éléments XML de votre carte sont dépendants sur le runtime contenu de votre carte. Et comme XML est généralement utilisé comme une interface externe ou de la couche d'interface ce n'est pas souhaitable. Laissez-moi vous expliquer.

Le Schéma Xml (xsd) définit l'interface du contrat de vos documents XML. En plus d'être en mesure de générer le code à partir du fichier XSD, JAXB peut aussi générer un schéma XML pour vous à partir du code. Cela vous permet de limiter la quantité de données échangées via l'interface convenu à l'avance, les structures définies dans le fichier XSD.

Dans le cas par défaut pour un Map<String, String>, à la génération XSD va restreindre l'élément de carte de contenir plusieurs éléments de saisie qui doivent contenir un xs:string clé et un xs:string de la valeur. C'est une jolie interface claire contrat.

Ce que vous décrivez, c'est que vous voulez que le mappage xml contenir des éléments dont le nom sera déterminé par le contenu de la carte au moment de l'exécution. Alors générés XSD peut seulement indiquer que la carte doit contenir une liste des éléments dont le type est inconnu au moment de la compilation. C'est quelque chose que vous devriez éviter lors de la définition d'une interface de contrat.

Pour obtenir un contrat strict dans ce cas, vous devez utiliser un type énuméré comme la clé de la carte au lieu d'une Chaîne. E. g.

public enum KeyType {
 KEY, KEY2;
}

@XmlJavaTypeAdapter(MapAdapter.class)
Map<KeyType , String> mapProperty;

De cette façon, les clés qui vous voulez devenir des éléments XML sont connus au moment de la compilation afin de JAXB devrait être en mesure de générer un schéma qui permettrait de limiter les éléments de la carte pour les éléments à l'aide de l'une des clés prédéfinies de la CLÉ ou de la TOUCHE2.

D'autre part, si vous souhaitez simplifier par défaut généré structure

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

Pour quelque chose de plus simple comme cela

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

Vous pouvez utiliser un MapAdapter qui convertit la Carte à un tableau de MapElements comme suit:

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

25voto

Grégory Points 554

le code fourni ne fonctionne pas pour moi. J'ai trouvé un autre moyen à la Carte :

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

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

}

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

16voto

Blaise Doughan Points 75613

Je suis toujours travailler sur une meilleure solution, mais à l'aide de MOXy JAXB, j'ai été capable de gérer le XML suivant:

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

Vous devez utiliser un @XmlJavaTypeAdapter sur votre Carte de propriété:

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 mise en œuvre de la XmlAdapter est comme suit:

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

}

Le AdpatedMap classe est là toute la magie se produit, nous allons utiliser le DOM pour représenter le contenu. Nous tromper JAXB intro affaire avec un DOM grâce à la combinaison de @XmlAnyElement et une propriété de type de l'Objet:

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 la MOXy JAXB mise en œuvre. Vous pouvez configurer le JAXB exécution pour utiliser le MOXy mise en œuvre par l'ajout d'un fichier nommé jaxb.propriétés avec votre modèle de classes avec l'entrée suivante:

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

La démonstration suivante de code 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(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);
    }
}

3voto

lukens Points 38

(Désolé, ne peut pas ajouter des commentaires)

Dans Blaise réponse ci-dessus, si vous modifiez:

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

pour:

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

ensuite, cela devrait se débarrasser de l' <mapProperty> balise, et ainsi de vous donner:

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

SINON:

Vous pouvez également le modifier:

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

et puis vous pouvez vous débarrasser de l' AdaptedMap tout à fait, et il suffit de changer MapAdapter de marshall à un Document objet directement. J'ai uniquement testé avec de l'ordonnancement, de sorte qu'il peut être unmarshalling questions.

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

2voto

lunicon Points 141

J'ai la solution sans adaptateur. Transitoire carte convertis au format xml-éléments et vice versa:

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

Ma sortie comme espected:

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

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

Vous pouvez utiliser l'élément de la paire nom/valeur. J'ai besoin d'attributs... Amusez-vous!

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X