Search code examples
javaxml-deserializationjackson-dataformat-xml

Java - Jackson XML Deserializing by Interface/Abstract class


I have these 2 XML's that I'm trying to deserialize with jackson-dataformat-xml:

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

These are the 2 XML files:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<query queryname="persons">
    <row>
        <name>charles</name>
        <userid>1</userid>
    </row>
    <row>
        <name>arthur</name>
        <userid>2</userid>
    </row>
</query>
<?xml version="1.0" encoding="ISO-8859-1" ?>
<query queryname="users">
    <row>
        <id>1</id>
        <username>charles</username>
    </row>
    <row>
        <id>2</id>
        <username>arthur</username>
    </row>
</query>

The 2 XMLs display the results of 2 different queries and are differentiated by the queryname attribute of the query element and the row element contains different information. I'm trying to deserialize the 2 XMLs with a single Query class like this:

public interface IRow {
    
}
public class Person implements IRow {
    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() + "]"; 
    }
}
public class User extends IRow {
    private String id;
    private String username;
    
    public String getId() {return id;}
    public String getUserName() {return username;}
    
    @Override
    public String toString() {
        return "user [id=" + getId() + ", username=" + getUserName() + "]"; 
    }
}
public class Query {
    @JacksonXmlProperty(isAttribute = true)
    private String queryname;
    @JacksonXmlElementWrapper(useWrapping = false)
    private List<IRow> row;
    
    public String getQueryname() {return queryname;}
    public List<IRow> getRow() {return row;}

    @Override
    public String toString() {
        return "query [queryname=" + getQueryname() + ", row=" + getRow() + "]"; 
    }
}
public class Main {
    public static void main(String[] args) throws JsonMappingException, JsonProcessingException {
        XmlMapper xmlMapper = new XmlMapper();
        String sXml = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\r\n"
                + "<query queryname=\"persons\">\r\n"
                + "    <row>\r\n"
                + "        <name>charles</name>\r\n"
                + "        <userid>1</userid>\r\n"
                + "    </row>\r\n"
                + "    <row>\r\n"
                + "        <name>arthur</name>\r\n"
                + "        <userid>2</userid>\r\n"
                + "    </row>\r\n"
                + "</query>";
        Query oQuery = xmlMapper.readValue(sXml,Query.class);
        System.out.println(oQuery);
    }
}

I get the error The main class, rightly, throws the Exception error in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `xmlmanager.jackson.IRow` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information

The mistake is right, as in the Query class I defined the row property as a list of IRows. I would need something (I think annotations) that allow me to define and deserialize the row element with the class:

  • Person when attribute queryname = "persons"
  • User when attribute queryname = "users"

How could I do?

Thanks in advance


Solution

  • The Sascha's solution works even without the JsonTypeInfo and JsonSubTypes annotations and without using the IRow interface:

    import java.util.List;
    import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
    import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
    
    public abstract class AbstractQuery<I> {
        @JacksonXmlProperty(isAttribute = true)
        private String queryname;
        @JacksonXmlElementWrapper(useWrapping = false)
        private List<I> row;
        
        public String getQueryname() {return queryname;}
        public List<I> getRow() {return row;}
    
        @Override
        public String toString() {
            return "query [queryname=" + getQueryname() + ", row=" + getRow() + "]"; 
        }
    }
    
    public class Person {
        public static class Query extends AbstractQuery<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() + "]"; 
        }
    }
    
    import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
    
    public class User {
        public static class Query extends AbstractQuery<User> {}
        
        private String id;
        @JacksonXmlProperty(localName = "username")
        private String username;
        
        public String getId() {return id;}
        public String getUserName() {return username;}
        
        @Override
        public String toString() {
            return "user [id=" + getId() + ", username=" + getUserName() + "]"; 
        }
    }
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.JsonMappingException;
    import com.fasterxml.jackson.dataformat.xml.XmlMapper;
    
    public class Main {
        public static void main(String[] args) throws JsonMappingException, JsonProcessingException {
            XmlMapper xmlMapper = new XmlMapper();
            String sXmlPersons = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\r\n"
                    + "<query queryname=\"persons\">\r\n"
                    + "    <row>\r\n"
                    + "        <name>charles</name>\r\n"
                    + "        <userid>1</userid>\r\n"
                    + "    </row>\r\n"
                    + "    <row>\r\n"
                    + "        <name>arthur</name>\r\n"
                    + "        <userid>2</userid>\r\n"
                    + "    </row>\r\n"
                    + "</query>";
            Person.Query oPersonQuery = xmlMapper.readValue(sXmlPersons,Person.Query.class);
            System.out.println(oPersonQuery);
            
            String sXmlUsers = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\r\n"
                    + "<query queryname=\"users\">\r\n"
                    + "    <row>\r\n"
                    + "        <id>1</id>\r\n"
                    + "        <username>charles</username>\r\n"
                    + "    </row>\r\n"
                    + "    <row>\r\n"
                    + "        <id>2</id>\r\n"
                    + "        <username>arthur</username>\r\n"
                    + "    </row>\r\n"
                    + "</query>";
            User.Query oUserQuery = xmlMapper.readValue(sXmlUsers,User.Query.class);
            System.out.println(oUserQuery);
        }
    }