Skip to content

Empty (or self-closing) Element representing List is incorrectly deserialized as null, not Empty List #252

@sir4ur0n

Description

@sir4ur0n

Following discussion between @allienna and @cowtowncoder on #1402 I'm creating an issue.

Hi guys,

While using Jackson with JAXB bindings, I found deserialization behaves unexpectedly on empty or self-closing elements: it sets sub-fields as null no matter what.

Here's a class to reproduce the issue (sorry if it's big):

import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertThat;

import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.Nulls;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.junit.Before;
import org.junit.Test;

public class Main {

  private XmlMapper mapper;

  @Before
  public void setUp() throws Exception {
    mapper = new XmlMapper();
    mapper.setDefaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.SKIP));
    mapper.setAnnotationIntrospector(new JaxbAnnotationIntrospector(TypeFactory.defaultInstance()));
  }

  @Test
  public void testWithValues() throws Exception {
    String toDeserialize = "<pojos><pojo>a</pojo><pojo>b</pojo><pojo>c</pojo></pojos>";

    Pojos pojos = mapper.readValue(toDeserialize, Pojos.class);
    assertThat(pojos, notNullValue());
    assertThat(pojos.getValues(), notNullValue());
    assertThat(pojos.getValues().size(), is(3));
  }

  @Test
  public void testWithoutValues() throws Exception {
    String toDeserialize = "<pojos></pojos>";

    Pojos pojos = mapper.readValue(toDeserialize, Pojos.class);
    assertThat(pojos, notNullValue());
    assertThat(pojos.getValues(), notNullValue());
    assertThat(pojos.getValues().size(), is(0));
  }

  @Test
  public void testSelfClosingWithoutValues() throws Exception {
    String toDeserialize = "<pojos/>";

    Pojos pojos = mapper.readValue(toDeserialize, Pojos.class);
    assertThat(pojos, notNullValue());
    assertThat(pojos.getValues(), notNullValue());
    assertThat(pojos.getValues().size(), is(0));
  }

  @Test
  public void testWrapperWithValues() throws Exception {
    String toDeserialize = "<wrapper><pojos><pojo>a</pojo><pojo>b</pojo><pojo>c</pojo></pojos></wrapper>";

    Wrapper wrapper = mapper.readValue(toDeserialize, Wrapper.class);
    assertThat(wrapper, notNullValue());
    assertThat(wrapper.getPojos(), notNullValue());
    assertThat(wrapper.getPojos().getValues(), notNullValue());
    assertThat(wrapper.getPojos().getValues().size(), is(3));
    assertThat(wrapper.getPojos().getValues().get(0).getValue(), is("a"));
  }

  @Test
  public void testWrapperWithoutValues() throws Exception {
    String toDeserialize = "<wrapper><pojos></pojos></wrapper>";

    Wrapper wrapper = mapper.readValue(toDeserialize, Wrapper.class);
    assertThat(wrapper, notNullValue());
    assertThat(wrapper.getPojos(), notNullValue());
    assertThat(wrapper.getPojos().getValues(), notNullValue());
    assertThat(wrapper.getPojos().getValues().size(), is(0));
  }

  @Test
  public void testWrapperWithSelfClosingPojos() throws Exception {
    String toDeserialize = "<wrapper><pojos/></wrapper>";

    Wrapper wrapper = mapper.readValue(toDeserialize, Wrapper.class);
    assertThat(wrapper, notNullValue());
    assertThat(wrapper.getPojos(), notNullValue());
    assertThat(wrapper.getPojos().getValues(), notNullValue());
    assertThat(wrapper.getPojos().getValues().size(), is(0));
  }

  @Test
  public void testWrapperWithoutPojos() throws Exception {
    String toDeserialize = "<wrapper></wrapper>";

    Wrapper wrapper = mapper.readValue(toDeserialize, Wrapper.class);
    assertThat(wrapper, notNullValue());
    assertThat(wrapper.getPojos(), notNullValue());
    assertThat(wrapper.getPojos().getValues(), notNullValue());
    assertThat(wrapper.getPojos().getValues().size(), is(0));
  }

  @Test
  public void testSelfClosingWrapper() throws Exception {
    String toDeserialize = "<wrapper/>";

    Wrapper wrapper = mapper.readValue(toDeserialize, Wrapper.class);
    assertThat(wrapper, notNullValue());
    assertThat(wrapper.getPojos(), notNullValue());
    assertThat(wrapper.getPojos().getValues(), notNullValue());
    assertThat(wrapper.getPojos().getValues().size(), is(0));
  }

  @XmlRootElement(name = "wrapper")
  @XmlAccessorType(XmlAccessType.FIELD)
  private static class Wrapper {
    @XmlElement(name = "pojos")
    private Pojos pojos = new Pojos();

    @java.beans.ConstructorProperties({"pojos"})
    private Wrapper(Pojos pojos) {
      this.pojos = pojos;
    }

    public Wrapper() {
    }

    public Pojos getPojos() {
      return this.pojos;
    }

    public void setPojos(Pojos pojos) {
      this.pojos = pojos;
    }

  }

  @XmlAccessorType(XmlAccessType.FIELD)
  private static class Pojos {
    @XmlElement(name = "pojo")
    private java.util.List<Pojo> values = new ArrayList<>();

    @java.beans.ConstructorProperties({"values"})
    private Pojos(List<Pojo> values) {
      this.values = values;
    }

    public Pojos() {
    }

    public List<Pojo> getValues() {
      return this.values;
    }

    public void setValues(List<Pojo> values) {
      this.values = values;
    }

  }

  @XmlAccessorType(XmlAccessType.FIELD)
  private static class Pojo {
    private String value = "";

    @java.beans.ConstructorProperties({"value"})
    private Pojo(String value) {
      this.value = value;
    }

    public Pojo() {
    }

    public String getValue() {
      return this.value;
    }

    public void setValue(String value) {
      this.value = value;
    }

  }
}

And the Maven pom.xml (without Lombok as requested by @cowtowncoder):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>test</groupId>
  <artifactId>test</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.6.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>com.fasterxml.jackson.dataformat</groupId>
      <artifactId>jackson-dataformat-xml</artifactId>
      <version>2.9.0.pr4</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.9.0.pr4</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.0.pr4</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.9.0.pr4</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.module</groupId>
      <artifactId>jackson-module-jaxb-annotations</artifactId>
      <version>2.9.0.pr4</version>
    </dependency>
    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-core</artifactId>
      <version>2.7.9</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

</project>

If you run it, you will see that 2 tests fail:

  • testWrapperWithoutValues
  • testWrapperWithSelfClosingPojos

Now I'm anything but a JAXB or Jackson expert, so I definitely may have done mistakes or misconfigured my mapper or POJOs, in that case, any hint is appreciated.
To my surprise, <wrapper><pojos></pojos></wrapper> returns a null Pojos object, while <wrapper></wrapper> returns an instance of Pojos (as you can see in testWrapperWithoutPojos test).
I think this is a bug.

Dependencies to ensure we test in the same conditions:
org.projectlombok:lombok:jar:1.16.14
org.mockito:mockito-core:jar:2.7.9
com.fasterxml.jackson.dataformat:jackson-dataformat-xml:jar:2.9.0.pr4
com.fasterxml.jackson.core:jackson-core:jar:2.9.0.pr4
com.fasterxml.jackson.core:jackson-annotations:jar:2.9.0.pr4
com.fasterxml.jackson.module:jackson-module-jaxb-annotations:jar:2.9.0.pr4

Note I also tried with jackson 2.8.9 version (for all Jackson-related libraries obviously) without this line:

mapper.setDefaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.SKIP));

and the problem remains.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions