Konrad Morawski
Konrad Morawski

Reputation: 8394

How to deserialize xml into a map of custom attributes (and their values) with SimpleXml?

I am using SimpleXml.

The xml I am deserializing looks roughly like so:

<?xml version="1.0" encoding="UTF-8"?> 
<test a="1" e="2" f="5"/>

That the attributes happen to be a, e and f is not known at runtime - could be q and z just as well.

The class definition:

@Root (strict = false)
public class Test {
   @ElementMap (entry = "test", attribute = true)
   public HashMap<String, Integer> map;
}

I expect Test.map to contain "a" -> 1, "b" -> 2 and "f" -> 5 after deserialization.

Instead, I keep on getting an exception: unnable to satisfy @org.simpleframework.xml.ElementMap ... on field 'map' ... for class Test ... (fluff removed - exception message doesn't contain any further clarification).

I tried to fiddle with various attributes of ElementMap (inlining, not inlining etc.), but to no success so far.

(It is actually the case that the values happen to be numeric, although that's circumstantial and I'd be fine going with string values to parse them on my own if that's necessary - not sure if it matters at all here though.)

What's the solution?

And if SimpleXml doesn't offer any out of the box, what's the suggested workaround?

Upvotes: 3

Views: 999

Answers (1)

Konrad Morawski
Konrad Morawski

Reputation: 8394

I solved it with the use of a Converter, which allows to perform the deserialization manually.

So now the model is:

public class Test {
   @Convert (MapConverter.class)
   @Element (name = "test")
   public HashMap<String, Integer> test;
}

And MapConverter:

public class MapConverter implements Converter<HashMap<String, Integer>> {
   @Override
   public HashMap<String, Integer> read(InputNode inputNode) throws Exception {
      final HashMap<String, Integer> result = new HashMap<String, Integer>();

      for (final String attributeName : inputNode.getAttributes()) {
         final String value = inputNode.getAttribute(attributeName).getValue();
         result.put(attributeName, Integer.parseInt(value));
      }

      return result;
   }

   @Override
   public void write(OutputNode outputNode, HashMap<String, Integer> stringIntegerHashMap)
         throws Exception {
      // not used
   }
}

Note that for this to work you have to pass an instance of AnnotationStrategy to Persister:

// instead of new Persister()
Persister persister = new Persister(new AnnotationStrategy()); 

I'm not that keen on this solution, because

  1. I suspect it could be an overkill

  2. Initializing a Persister is done deep inside a preexisting library that I have to be using, so it required me to crack the code open in order to get it to work at all.

The fact that Persister ignores one of the framework's native annotation by default makes for a confusing API (even though this behaviour is documented). But that's a different story.

I wonder if someone comes up with something better.

Upvotes: 2

Related Questions