FasterXML / jackson-dataformat-xml

Extension for Jackson JSON processor that adds support for serializing POJOs as XML (and deserializing from XML) as an alternative to JSON
Apache License 2.0
567 stars 221 forks source link

List serialization without @XmlElementWrapper not working correctly (need to enable `XmlMapper.defaultUseWrapper(false)`) #589

Closed toellrich closed 1 year ago

toellrich commented 1 year ago

When serializing a list without @XmlElementWrapper, the list is still wrapped inside another element. The following unit test reproduces the problem:

public class ListWithoutElementWrapperTest {

  @XmlRootElement(name = "employee")
  @XmlType(propOrder = {"hobbies", "id", "firstName", "lastName"})
  @XmlAccessorType(FIELD)
  public static class Employee {

    @XmlElement(name = "hobby")
    List<String> hobbies;

    Integer id;
    String firstName;
    String lastName;
  }

  private Employee employee;

  @BeforeEach
  public void setUp() {
    employee = new Employee();
    employee.id = 1;
    employee.firstName = "Lokesh";
    employee.lastName = "Gupta";
    employee.hobbies = List.of("Swimming", "Playing", "Karate");
  }

  @Test
  public void marshallUsingJaxb() throws JAXBException {
    JAXBContext jaxbContext = JAXBContext.newInstance(Employee.class);
    Marshaller marshaller = jaxbContext.createMarshaller();
    marshaller.setProperty(JAXB_FORMATTED_OUTPUT, TRUE);
    marshaller.setProperty(JAXB_FRAGMENT, Boolean.TRUE);

    String expected =
        """
        <employee>
            <hobby>Swimming</hobby>
            <hobby>Playing</hobby>
            <hobby>Karate</hobby>
            <id>1</id>
            <firstName>Lokesh</firstName>
            <lastName>Gupta</lastName>
        </employee>""";

    StringWriter sw = new StringWriter();
    marshaller.marshal(employee, sw);
    String actual = sw.toString();
    assertEquals(expected, actual);
  }

  /**
   * This test fails
   */
  @Test
  public void writeValueAsStringUsingJackson() throws JsonProcessingException {
    XmlMapper xmlMapper = new XmlMapper();
    xmlMapper.enable(INDENT_OUTPUT);
    xmlMapper.registerModule(new JakartaXmlBindAnnotationModule());

    String expected =
        """
        <employee>
          <hobby>Swimming</hobby>
          <hobby>Playing</hobby>
          <hobby>Karate</hobby>
          <id>1</id>
          <firstName>Lokesh</firstName>
          <lastName>Gupta</lastName>
        </employee>""";

    String actual = xmlMapper.writeValueAsString(employee);
    assertEquals(expected, actual);
  }
}

The second test when using Jackson instead of JAXB fails because the actual output is as follows:

<employee>
  <hobby>
    <hobby>Swimming</hobby>
    <hobby>Playing</hobby>
    <hobby>Karate</hobby>
  </hobby>
  <id>1</id>
  <firstName>Lokesh</firstName>
  <lastName>Gupta</lastName>
</employee>

but I would expect it to be like this:

<employee>
  <hobby>Swimming</hobby>
  <hobby>Playing</hobby>
  <hobby>Karate</hobby>
  <id>1</id>
  <firstName>Lokesh</firstName>
  <lastName>Gupta</lastName>
</employee>

I'm using java 17 and jackson 2.15.0-rc1.

test.zip

cowtowncoder commented 1 year ago

Yes, Jackson defaults to wrapping Lists by default. This is different from JAXB defaults, but Jackson is not a JAXB implementation; it has partial support for JAXB annotations but does not have exactly same defaulting.

There is a way to change this default; I forget the method but there's one in XMLMapper.

toellrich commented 1 year ago

Thanks, found it. It is

XmlMapper mapper = XmlMapper.builder()
   .defaultUseWrapper(false)
   .build();

The issue can be closed.

cowtowncoder commented 1 year ago

Thank you for confirming @toellrich !