Search code examples
javagenericsjackson-dataformat-xml

Java - Jackson XML Deserializing with generics


I'm trying to deserialize some XML using generics and I get an unexpected result. Here is a simplified example of the code I'm using:

Maven dependecy

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.13.3</version>
</dependency>

Xml to deserialize

<?xml version="1.0" encoding="ISO-8859-1" ?>
<xml_result>
    <row>
        <name>charles</name>
        <userid>1</userid>
    </row>
    <row>
        <name>arthur</name>
        <userid>2</userid>
    </row>
</xml_result>

The row element can contain different information. The example contains people information

Generic class

import java.util.List;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;

public class XmlGeneric<T> {
    @JacksonXmlProperty(localName = "row")
    @JacksonXmlElementWrapper(useWrapping = false)
    private List<T> rows;
    public List<T> getRow() {return rows;};
    
    @Override
    public String toString() {
        return "XmlGeneric [rows=" + rows + "]"; 
    }
 }

POJO Person

public class Person {
    private String name;
    private String userid;
    
    public String getName() {return name;}
    public String getUserid() {return userid;}
    
    @Override
    public String toString() {
        return "person [name=" + getName() + ", userid=" + getUserid() + "]";
    }
}

If I use the following code, the deserialization is successful

XmlMapper xmlMapper = new XmlMapper();
XmlGeneric<Person> xmlPerson = xmlMapper.readValue(xmlPersonData,new TypeReference<XmlGeneric<Person>>() {});
System.out.println(xmlPerson);
// Result: XmlGeneric [rows=[person [name=charles, userid=1], person [name=arthur, userid=2]]]

For project needs, however, I need the deserialization to be done in another class. I tried this, but the result is not what I wanted

public class MyXmlManager<T> {
    final static XmlMapper oXmlMapper = new XmlMapper();
    
    public XmlGeneric<T> xmlDeserialization(String sXmlData) throws JsonMappingException, JsonProcessingException {
        return oXmlMapper.readValue(sXmlData,new TypeReference<XmlGeneric<T>>() {});
    }
}
MyXmlManager<Person> myXmlManager = new MyXmlManager<Person>();
XmlGeneric<Person> xmlPerson = myXmlManager.xmlDeserialization(xmlPersonData);
System.out.println(xmlPerson);
// Result: XmlGeneric [rows=[{name=charles, userid=1}, {name=arthur, userid=2}]]

As you can see, it does not deserialize the row elements in the Person POJO, but returns a LinkedHashMap with its value corresponding to a JSON format ({name=charles, userid=1}). In fact, trying to cast, I get the error java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to Person.

I know that I could use the com.fasterxml.jackson.databind.ObjectMapper object to deserialize the JSON in the Person POJO, but I would like to understand why using the MyXmlManager<T> class the deserialization doesn't work correctly. It is as if the generic type passed to instantiate the MyXmlManager was not correctly passed to the XmlMapper.readValue method.

Thanks in advance


Solution

  • I found the solution, I report it below, maybe it can help someone:

    • add a Class<T> property to the MyXmlManager<T> class
    • add a constructor with parameter Class<T> to instantiate the added property
    • compile the type to be passed to the XmlMapper.readValue method using the mapper.getTypeFactory().constructParametricType method.

    This is the modified code:

    public class MyXmlManager<T> {
        final static XmlMapper oXmlMapper = new XmlMapper();
        private Class<T> clazz;
        
        public MyXmlManager(Class<T> clazz) {
            this.clazz = clazz;
        }
        
        public XmlGeneric<T> xmlDeserialization(String sXmlData) throws JsonMappingException, JsonProcessingException, InstantiationException, IllegalAccessException {
            return oXmlMapper.readValue(sXmlData, oXmlMapper.getTypeFactory().constructParametricType(XmlGeneric.class, clazz));
        }
    }
    

    Test

    MyXmlManager<Person> myXmlManager = new MyXmlManager<Person>(Person.class);
    XmlGeneric<Person> xmlPerson = myXmlManager.xmlDeserialization(xmlPersonData);
    System.out.println(xmlPerson);
    // Result: XmlGeneric [rows=[person [name=charles, userid=1], person [name=arthur, userid=2]]