Je recherche une API légère (une classe unique préférable) pour convertir un
Map<String,String> map = new HashMap<String,String();
en xml et inversement, reconvertit le XML en Map.
exemple:
Map<String,String> map = new HashMap<String,String();
map.put("name","chris");
map.put("island","faranga");
MagicAPI.toXML(map,"root");
résultat:
<root>
<name>chris</chris>
<island>faranga</island>
</root>
et retour:
Map<String,String> map = MagicAPI.fromXML("...");
Je ne veux pas utiliser JAXB ou API de conversion JSON . Il ne faut pas s'occuper des cartes imbriquées ou des attributs ou quoi que ce soit d'autre, juste ce cas simple . Des suggestions?
Edit: j'ai créé un exemple de copier-coller de travail. Merci à fvu et Michal Bernhard .
Téléchargez le dernier framework XStream , 'core only' suffit.
Map<String,Object> map = new HashMap<String,Object>();
map.put("name","chris");
map.put("island","faranga");
// convert to XML
XStream xStream = new XStream(new DomDriver());
xStream.alias("map", Java.util.Map.class);
String xml = xStream.toXML(map);
// from XML, convert back to map
Map<String,Object> map2 = (Map<String,Object>) xStream.fromXML(xml);
Aucun convertisseur ou autre chose n'est requis. Juste le xstream-x.y.z.jar est suffisant.
XStream!
Mise à jour : J'ai ajouté la partie unmarshal comme demandé dans les commentaires.
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import Java.util.AbstractMap;
import Java.util.HashMap;
import Java.util.Map;
public class Test {
public static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>();
map.put("name","chris");
map.put("island","faranga");
XStream magicApi = new XStream();
magicApi.registerConverter(new MapEntryConverter());
magicApi.alias("root", Map.class);
String xml = magicApi.toXML(map);
System.out.println("Result of tweaked XStream toXml()");
System.out.println(xml);
Map<String, String> extractedMap = (Map<String, String>) magicApi.fromXML(xml);
assert extractedMap.get("name").equals("chris");
assert extractedMap.get("island").equals("faranga");
}
public static class MapEntryConverter implements Converter {
public boolean canConvert(Class clazz) {
return AbstractMap.class.isAssignableFrom(clazz);
}
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
AbstractMap map = (AbstractMap) value;
for (Object obj : map.entrySet()) {
Map.Entry entry = (Map.Entry) obj;
writer.startNode(entry.getKey().toString());
Object val = entry.getValue();
if ( null != val ) {
writer.setValue(val.toString());
}
writer.endNode();
}
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Map<String, String> map = new HashMap<String, String>();
while(reader.hasMoreChildren()) {
reader.moveDown();
String key = reader.getNodeName(); // nodeName aka element's name
String value = reader.getValue();
map.put(key, value);
reader.moveUp();
}
return map;
}
}
}
Voici le convertisseur pour XStream incluant unmarshall
public class MapEntryConverter implements Converter{
public boolean canConvert(Class clazz) {
return AbstractMap.class.isAssignableFrom(clazz);
}
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
AbstractMap<String,String> map = (AbstractMap<String,String>) value;
for (Entry<String,String> entry : map.entrySet()) {
writer.startNode(entry.getKey().toString());
writer.setValue(entry.getValue().toString());
writer.endNode();
}
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Map<String, String> map = new HashMap<String, String>();
while(reader.hasMoreChildren()) {
reader.moveDown();
map.put(reader.getNodeName(), reader.getValue());
reader.moveUp();
}
return map;
}
Que diriez-vous de XStream ? Pas une classe, mais deux bocaux pour de nombreux cas d'utilisation, y compris le vôtre, très simple à utiliser et pourtant assez puissant.
Une option serait de rouler le vôtre. Ce serait assez simple à faire:
Document doc = getDocument();
Element root = doc.createElement(rootName);
doc.appendChild(root);
for (Map.Entry<String,String> element : map.entrySet() ) {
Element e = doc.createElement(element.getKey());
e.setTextContent(element.getValue());
root.appendChild(e);
}
save(doc, file);
et la charge est également une getChildNodes
et une boucle. Bien sûr, il a un peu de chaudière que les dieux XML exigent, mais il faut au plus 1 heure de travail.
Ou vous pouvez regarder Propriétés si vous n'êtes pas trop fusionné sur le format du XML.
J'ai utilisé l'approche avec le convertisseur personnalisé:
public static class MapEntryConverter implements Converter {
public boolean canConvert(Class clazz) {
return AbstractMap.class.isAssignableFrom(clazz);
}
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
AbstractMap map = (AbstractMap) value;
for (Object obj : map.entrySet()) {
Entry entry = (Entry) obj;
writer.startNode(entry.getKey().toString());
context.convertAnother(entry.getValue());
writer.endNode();
}
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
// dunno, read manual and do it yourself ;)
}
}
Mais j'ai changé la valeur de sérialisation de la carte pour déléguer à MarshallingContext. Cela devrait améliorer la solution afin qu'elle fonctionne également pour les valeurs de carte composite et les cartes imbriquées.
J'ai écrit un morceau de code qui transforme un contenu XML en une structure multicouche de cartes:
public static Object convertNodesFromXml(String xml) throws Exception {
InputStream is = new ByteArrayInputStream(xml.getBytes());
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.parse(is);
return createMap(document.getDocumentElement());
}
public static Object createMap(Node node) {
Map<String, Object> map = new HashMap<String, Object>();
NodeList nodeList = node.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node currentNode = nodeList.item(i);
String name = currentNode.getNodeName();
Object value = null;
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
value = createMap(currentNode);
}
else if (currentNode.getNodeType() == Node.TEXT_NODE) {
return currentNode.getTextContent();
}
if (map.containsKey(name)) {
Object os = map.get(name);
if (os instanceof List) {
((List<Object>)os).add(value);
}
else {
List<Object> objs = new LinkedList<Object>();
objs.add(os);
objs.add(value);
map.put(name, objs);
}
}
else {
map.put(name, value);
}
}
return map;
}
Ce code transforme ceci:
<house>
<door>blue</door>
<living-room>
<table>wood</table>
<chair>wood</chair>
</living-room>
</house>
dans
{
"house": {
"door": "blue",
"living-room": {
"table": "wood",
"chair": "wood"
}
}
}
Je n'ai pas le processus inverse, mais cela ne doit pas être très difficile à écrire.
Je publie ceci comme une réponse, non pas parce que c’est la bonne réponse à votre question, mais parce que c’est une solution au même problème, mais l’utilisation d’attributs. Sinon, la réponse de Vikas Gujjar est correcte.
Bien souvent, vos données peuvent être en attributs, mais il est assez difficile de trouver des exemples de travail utilisant XStream pour le faire, alors en voici un:
Échantillon de données:
<settings>
<property name="prop1" value="foo"/>
<property name="prop2" /> <!-- NOTE:
The example supports null elements as
the backing object is a HashMap.
A Properties object would be handled
by a PropertiesConverter which wouldn't
allow you null values. -->
<property name="prop3" value="1"/>
</settings>
Implémentation de MapEntryConverter (implémentation légèrement modifiée de @Vikas Gujjar pour utiliser des attributs):
public class MapEntryConverter
implements Converter
{
public boolean canConvert(Class clazz)
{
return AbstractMap.class.isAssignableFrom(clazz);
}
public void marshal(Object value,
HierarchicalStreamWriter writer,
MarshallingContext context)
{
//noinspection unchecked
AbstractMap<String, String> map = (AbstractMap<String, String>) value;
for (Map.Entry<String, String> entry : map.entrySet())
{
//noinspection RedundantStringToString
writer.startNode(entry.getKey().toString());
//noinspection RedundantStringToString
writer.setValue(entry.getValue().toString());
writer.endNode();
}
}
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context)
{
Map<String, String> map = new HashMap<String, String>();
while (reader.hasMoreChildren())
{
reader.moveDown();
map.put(reader.getAttribute("name"), reader.getAttribute("value"));
reader.moveUp();
}
return map;
}
}
Configuration, analyse et stockage d'instance XStream:
XStream xstream = new XStream();
xstream.autodetectAnnotations(true);
xstream.alias("settings", HashMap.class);
xstream.registerConverter(new MapEntryConverter());
...
// Parse:
YourObject yourObject = (YourObject) xstream.fromXML(is);
// Store:
xstream.toXML(yourObject);
...
J'ai trouvé ceci sur Google, mais je ne veux pas utiliser XStream, car cela entraîne beaucoup de surcharge dans mon environnement. Je n'avais besoin que d'analyser un fichier et comme je ne trouvais rien qui me plaisait, j'ai créé ma propre solution simple pour analyser un fichier du format que vous décrivez. Alors voici ma solution:
public class XmlToMapUtil {
public static Map<String, String> parse(InputSource inputSource) throws SAXException, IOException, ParserConfigurationException {
final DataCollector handler = new DataCollector();
SAXParserFactory.newInstance().newSAXParser().parse(inputSource, handler);
return handler.result;
}
private static class DataCollector extends DefaultHandler {
private final StringBuilder buffer = new StringBuilder();
private final Map<String, String> result = new HashMap<String, String>();
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
final String value = buffer.toString().trim();
if (value.length() > 0) {
result.put(qName, value);
}
buffer.setLength(0);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
buffer.append(ch, start, length);
}
}
}
Voici quelques tests TestNG + FEST Assert:
public class XmlToMapUtilTest {
@Test(dataProvider = "provide_xml_entries")
public void parse_returnsMapFromXml(String xml, MapAssert.Entry[] entries) throws Exception {
// execution
final Map<String, String> actual = XmlToMapUtil.parse(new InputSource(new StringReader(xml)));
// evaluation
assertThat(actual)
.includes(entries)
.hasSize(entries.length);
}
@DataProvider
public Object[][] provide_xml_entries() {
return new Object[][]{
{"<root />", new MapAssert.Entry[0]},
{
"<root><a>aVal</a></root>", new MapAssert.Entry[]{
MapAssert.entry("a", "aVal")
},
},
{
"<root><a>aVal</a><b>bVal</b></root>", new MapAssert.Entry[]{
MapAssert.entry("a", "aVal"),
MapAssert.entry("b", "bVal")
},
},
{
"<root> \t <a>\taVal </a><b /></root>", new MapAssert.Entry[]{
MapAssert.entry("a", "aVal")
},
},
};
}
}
J'ai essayé différents types de cartes et le Boîte de conversion a fonctionné. J'ai utilisé votre carte et ai collé un exemple ci-dessous avec des cartes intérieures. J'espère que cela vous sera utile ....
import Java.util.HashMap;
import Java.util.Map;
import cjm.component.cb.map.ToMap;
import cjm.component.cb.xml.ToXML;
public class Testing
{
public static void main(String[] args)
{
try
{
Map<String, Object> map = new HashMap<String, Object>(); // ORIGINAL MAP
map.put("name", "chris");
map.put("island", "faranga");
Map<String, String> mapInner = new HashMap<String, String>(); // SAMPLE INNER MAP
mapInner.put("a", "A");
mapInner.put("b", "B");
mapInner.put("c", "C");
map.put("innerMap", mapInner);
Map<String, Object> mapRoot = new HashMap<String, Object>(); // ROOT MAP
mapRoot.put("ROOT", map);
System.out.println("Map: " + mapRoot);
System.out.println();
ToXML toXML = new ToXML();
String convertedXML = String.valueOf(toXML.convertToXML(mapRoot, true)); // CONVERTING ROOT MAP TO XML
System.out.println("Converted XML: " + convertedXML);
System.out.println();
ToMap toMap = new ToMap();
Map<String, Object> convertedMap = toMap.convertToMap(convertedXML); // CONVERTING CONVERTED XML BACK TO MAP
System.out.println("Converted Map: " + convertedMap);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
Sortie:
Map: {ROOT={name=chris, innerMap={b=B, c=C, a=A}, island=faranga}}
-------- Map Detected --------
-------- XML created Successfully --------
Converted XML: <ROOT><name>chris</name><innerMap><b>B</b><c>C</c><a>A</a></innerMap><island>faranga</island></ROOT>
-------- XML Detected --------
-------- Map created Successfully --------
Converted Map: {ROOT={name=chris, innerMap={b=B, c=C, a=A}, island=faranga}}
Underscore-Java library peut convertir Map en XML. Je suis le mainteneur du projet. Exemple live
Exemple de code:
import com.github.underscore.lodash.U;
import Java.util.*;
public class Main {
public static void main(String[] args) {
Map<String, Object> map = new LinkedHashMap<String, Object>();
map.put("name", "chris");
map.put("island", "faranga");
System.out.println(U.toXml(map));
// <root>
// <name>chris</chris>
// <island>faranga</island>
// </root>
}
}
Nous sommes en 2017, la dernière version de XStream nécessite un convertisseur pour fonctionner comme prévu
Un convertisseur prend en charge la carte imbriquée:
public class MapEntryConverter implements Converter {
@Override
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext marshallingContext) {
AbstractMap map = (AbstractMap) value;
for (Object obj : map.entrySet()) {
Map.Entry entry = (Map.Entry) obj;
writer.startNode(entry.getKey().toString());
Object val = entry.getValue();
if (val instanceof Map) {
marshal(val, writer, marshallingContext);
} else if (null != val) {
writer.setValue(val.toString());
}
writer.endNode();
}
}
@Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext unmarshallingContext) {
Map<String, Object> map = new HashMap<>();
while(reader.hasMoreChildren()) {
reader.moveDown();
String key = reader.getNodeName(); // nodeName aka element's name
String value = reader.getValue().replaceAll("\\n|\\t", "");
if (StringUtils.isBlank(value)) {
map.put(key, unmarshal(reader, unmarshallingContext));
} else {
map.put(key, value);
}
reader.moveUp();
}
return map;
}
@Override
public boolean canConvert(Class clazz) {
return AbstractMap.class.isAssignableFrom(clazz);
}
}
Dans mon cas, je convertis DBresponse en XML dans Camel ctx . L’exécuteur JDBC renvoie le ArrayList (lignes) avec LinkedCaseInsensitiveMap (une seule ligne). Task - crée un objet XML basé sur DBResponce.
import Java.io.StringWriter;
import Java.util.ArrayList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
public class ConvertDBToXMLProcessor implements Processor {
public void process(List body) {
if (body instanceof ArrayList) {
ArrayList<LinkedCaseInsensitiveMap> rows = (ArrayList) body;
DocumentBuilder builder = null;
builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = builder.newDocument();
Element rootElement = document.createElement("DBResultSet");
for (LinkedCaseInsensitiveMap row : rows) {
Element newNode = document.createElement("Row");
row.forEach((key, value) -> {
if (value != null) {
Element newKey = document.createElement((String) key);
newKey.setTextContent(value.toString());
newNode.appendChild(newKey);
}
});
rootElement.appendChild(newNode);
}
document.appendChild(rootElement);
/*
* If you need return string view instead org.w3c.dom.Document
*/
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
DOMSource domSource = new DOMSource(document);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.transform(domSource, result);
// return document
// return writer.toString()
}
}
}