admin管理员组

文章数量:1287515

I'm trying to deserialize an XML file into a Java object but, apparently, Jackson ignores the root element name.

I tried adding the @JacksonXmlRootElement annotation, following suggestions from these questions.

How can I make Jackson validate the root element name?


I tried to reproduce this behaviour with the following minimal, self-contained example:

customer.xml

<Customer>
    <FirstName>John</FirstName>
    <LastName>Smith</LastName>
</Customer>

Employee.java

@JacksonXmlRootElement(localName = "Employee")
public class Employee {
    @JacksonXmlProperty(localName = "FirstName")
    private String firstName;
    @JacksonXmlProperty(localName = "LastName")
    private String lastName;
    @JacksonXmlProperty(localName = "Salary")
    private BigDecimal salary;

    // getters and setters omitted for brevity...
}

Main method

XmlMapper mapper = new XmlMapper(new JacksonXmlModule());
String xmlContent = Files.readString(Path.of("customer.xml"));
Employee employee = mapper.readValue(xmlContent, Employee.class);

With the above code, Jackson will happily deserialize an XML document starting with the <Customer> element, despite @JacksonXmlRootElement(localName = "Employee") being present on the Employee POJO class.

Am I missing some Jackson module and/or XML mapper configuration?

I am using jackson-dataformat-xml 2.18.1.

I'm trying to deserialize an XML file into a Java object but, apparently, Jackson ignores the root element name.

I tried adding the @JacksonXmlRootElement annotation, following suggestions from these questions.

How can I make Jackson validate the root element name?


I tried to reproduce this behaviour with the following minimal, self-contained example:

customer.xml

<Customer>
    <FirstName>John</FirstName>
    <LastName>Smith</LastName>
</Customer>

Employee.java

@JacksonXmlRootElement(localName = "Employee")
public class Employee {
    @JacksonXmlProperty(localName = "FirstName")
    private String firstName;
    @JacksonXmlProperty(localName = "LastName")
    private String lastName;
    @JacksonXmlProperty(localName = "Salary")
    private BigDecimal salary;

    // getters and setters omitted for brevity...
}

Main method

XmlMapper mapper = new XmlMapper(new JacksonXmlModule());
String xmlContent = Files.readString(Path.of("customer.xml"));
Employee employee = mapper.readValue(xmlContent, Employee.class);

With the above code, Jackson will happily deserialize an XML document starting with the <Customer> element, despite @JacksonXmlRootElement(localName = "Employee") being present on the Employee POJO class.

Am I missing some Jackson module and/or XML mapper configuration?

I am using jackson-dataformat-xml 2.18.1.

Share Improve this question asked Feb 24 at 9:57 Danilo PiazzalungaDanilo Piazzalunga 7,8446 gold badges54 silver badges84 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

With the above code, Jackson will happily deserialize an XML document starting with the element, despite @JacksonXmlRootElement(localName = "Employee") being present on the Employee POJO class.

There is a misunderstanding about the use of the JacksonXmlRootElement and its localname attribute: the localname attribute is used not for deserialization process but only for serialization to change the root tag name like below:

@Data
//changed the localname to Othername
//this change will result to a root <OtherName> tag
@JacksonXmlRootElement(localName = "OtherName") 
public class Employee {
    @JacksonXmlProperty(localName = "FirstName")
    private String firstName;
    @JacksonXmlProperty(localName = "LastName")
    private String lastName;
    @JacksonXmlProperty(localName = "Salary")
    private BigDecimal salary;
}

public class Main {

    public static void main(String[] args) throws JsonProcessingException, JsonMappingException, XMLStreamException, IOException {
        String content = """
                     <Customer>
                         <FirstName>John</FirstName>
                         <LastName>Smith</LastName>
                     </Customer>
                     """;
        XmlMapper mapper = new XmlMapper();
        Employee employee = mapper.readValue(content, Employee.class);

        //it will print <OtherName><FirstName>John</FirstName><LastName>Smith</LastName><Salary/></OtherName>
        System.out.println(mapper.writeValueAsString(employee));      
}

With the above code, Jackson will happily deserialize an XML document starting with the element, despite @JacksonXmlRootElement(localName = "Employee") being present on the Employee POJO class.

Am I missing some Jackson module and/or XML mapper configuration?

From what I know there is no builtin configuration that can guarantee you a validation of the root tag name matching the classname of a Java object, but you can obtain the expected behaviour extending the XmlMapper class and creating a new method like below:

public class XmlMapperWithEvaluation extends XmlMapper {

    public <T> T readValueWithEvaluation(XMLStreamReader r, Class<T> valueType) throws IOException, XMLStreamException {
        String simpleName = valueType.getSimpleName();
        //point the root tag
        r.next();
        String localName = r.getLocalName();
        if (!simpleName.equals(localName)) {
            throw new JsonProcessingException("Classes names are different!") {
            };
        }

        return super.readValue(r, valueType); 
    }  
}

Then you can use it like the example below creating an XMLStreamReader to read the xml:

public class Main {

    public static void main(String[] args) throws JsonProcessingException, JsonMappingException, XMLStreamException, IOException {
        String content = """
                     <Customer>
                         <FirstName>John</FirstName>
                         <LastName>Smith</LastName>
                     </Customer>
                     """;

        //ok the mapper read the <Customer> tag and raise no exception
        XmlMapper mapper = new XmlMapper();
        Employee employee = mapper.readValue(content, Employee.class);
        System.out.println(mapper.writeValueAsString(employee));
        
        //create xmlstreamreader to read xml
        XMLInputFactory f = XMLInputFactory.newFactory();
        StringReader in = new StringReader(content);
        XMLStreamReader sr = f.createXMLStreamReader(in);
        
        XmlMapperWithEvaluation mapperWithEvaluation = new XmlMapperWithEvaluation();

        //ok the XmlMapperWithEvaluation mapper raise an exception
        //cause the root tag name <Customer> and Employee classname
        //are different
        employee = mapperWithEvaluation.readValueWithEvaluation(sr, Employee.class);
    }

}

public class XmlMapperWithEvaluation extends XmlMapper {

    public <T> T readValueWithEvaluation(XMLStreamReader r, Class<T> valueType) throws IOException, XMLStreamException {
        String simpleName = valueType.getSimpleName();
        r.next();
        String localName = r.getLocalName();
        if (!simpleName.equals(localName)) {
            throw new JsonProcessingException("Classes names are different!") {
            };
        }
        return super.readValue(r, valueType); 
    }
}

本文标签: javaJacksonXmlRootElement root element name ignored while deserializingStack Overflow