I want to serialize the class using xstream with the help of addImplicitMap method available in it. Class will look like:
class MapTest{
private Map<String, String> mapList;
public MapTest() {
mapList= new HashMap<String, String>();
}
public void setServicesHealth(String id, String name) {
map.put(id, name);
}
I tried like:
class MapTestMain{
public static void main(String args[]){
MapTest services = new MapTest();
services.setServicesHealth("ID01", "Jack");
services.setServicesHealth("ID02", "Neil);
XStream stream = new XStream(new StaxDriver());
stream.alias("MapTest", MapTest.class);
stream.addImplicitMap(MapTest.class, "map", "id", String.class, "name");
String xmlStr = stream.toXML(services);
System.out.println(xmlStr);
}
}
But I'm not getting the correct output. My expected output is like:
<?xml version="1.0" ?>
<MapTest>
<id>Started</id>
<name>Started</name>
</MapTest>
Kindly help me out...
TLDR: addImplicitMap
will not allow you to remove the <mapList>
container element and the entry
elements and rename the key/value elements all at once (example 1 and 2). For more control over naming of elements in maps, use NamedMapConverter
(example 3). To be able to do everything at once, you will need a custom Converter
implementation (example 4).
Long answer:
There doesn't appear to be a good explanation on SO of what addImplicitMap
does, and it's also not very clear in their documentation, so I'm going to try to explain here.
addImplicitMap
will remove the container element but, when used like this, it will not let you remove the <entry>
element or rename the key/value elements. For example: (1)
stream.addImplicitMap(MapTest.class, "mapList", "s", Map.Entry.class, null);
Results in:
<MapTest>
<s>
<string>ID01</string>
<string>Jack</string>
</s>
<s>
<string>ID02</string>
<string>Neil</string>
</s>
</MapTest>
Alternatively, addImplicitMap
lets you store only the values of a map when writing (omitting the container element and key elements). When reading XML it recreates the map keys using a specified field of the value objects, so the map key must be stored in the map value object.
For example, we can make the map's value a Service
object with an ID attribute and make Xstream store just the values and recreate the map from them: (2)
private Map<String, Service> serviceMap; // modified in MapTest
static class Service {
private String id, name;
public Service(String id, String name) {
this.id = id;
this.name = name;
}
}
MapTest services = new MapTest();
services.setService("ID01", new Service("ID01", "Jack"));
services.setService("ID02", new Service("ID02", "Neil"));
stream.addImplicitMap(MapTest.class, "serviceMap", "s", Service.class, "id");
Outputs:
<MapTest>
<s>
<id>ID01</id>
<name>Jack</name>
</s>
<s>
<id>ID02</id>
<name>Neil</name>
</s>
</MapTest>
Note that here each <s>
element is actually the serialized Service
object, not a Map.Entry
object.
This still doesn't quite solve your problem (and we had to change what was in the map too), so we can instead try the namedMapConverter
. If we go back to using a map of <String, String>
as you had originally, we can use: (3)
stream.registerConverter(new NamedMapConverter(stream.getMapper(),
null, "id", String.class, "name", String.class));
Which gives the output:
<MapTest>
<serviceMap>
<id>ID01</id>
<name>Jack</name>
<id>ID02</id>
<name>Neil</name>
</serviceMap>
</MapTest>
Still not quite right, and I don't believe you can make it better without implementing a custom Converter
class (there's a good tutorial here). So we add the line (instead of the NamedMapConverter
):
stream.registerConverter(new MapTestConverter());
And use a custom Converter implementation: (4)
static class MapTestConverter implements Converter {
public boolean canConvert(Class type) {
return type.equals(MapTest.class);
}
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
MapTest mt = (MapTest) source;
for (Entry<String, String> e : mt.serviceMap.entrySet()) {
writer.startNode("id");
writer.setValue(e.getKey());
writer.endNode();
writer.startNode("name");
writer.setValue(e.getValue());
writer.endNode();
}
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
MapTest mt = new MapTest();
String id = null;
while (reader.hasMoreChildren()) {
reader.moveDown();
if ("id".equals(reader.getNodeName())) {
if (id != null) { throw new RuntimeException("Malformed XML, ID was set twice: " + id); }
id = (String) context.convertAnother(mt, String.class);
} else if ("name".equals(reader.getNodeName())) {
String name = (String) context.convertAnother(mt, String.class);
if (id == null) { throw new RuntimeException("Malformed XML: Found name without ID: " + name); }
mt.serviceMap.put(id, name);
id = null;
}
reader.moveUp();
}
return mt;
}
}
Finally we get the desired result:
<MapTest>
<id>ID01</id>
<name>Jack</name>
<id>ID02</id>
<name>Neil</name>
</MapTest>
(sorry I don't have enough rep to make the following into links)
Full code for addImplicitMap
example: pastebin.com/MYiAde3m
Full code for namedMapConverter
example: pastebin.com/kVChup5x
Full code for Converter
example: pastebin.com/vUTwaHkk