Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Empty (or self-closing) element is incorrectly deserialized as null #1707

Closed
sir4ur0n opened this issue Jul 20, 2017 · 2 comments
Closed

Empty (or self-closing) element is incorrectly deserialized as null #1707

sir4ur0n opened this issue Jul 20, 2017 · 2 comments

Comments

@sir4ur0n
Copy link

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 javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.Before;
import org.junit.Test;

public class JacksonSkipNullBug {

  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));
  }

  @Data
  @XmlRootElement(name = "wrapper")
  @XmlAccessorType(XmlAccessType.FIELD)
  @AllArgsConstructor(access = AccessLevel.PRIVATE)
  @NoArgsConstructor
  private static class Wrapper {
    @XmlElement(name = "pojos")
    private Pojos pojos = new Pojos();
  }

  @Data
  @XmlAccessorType(XmlAccessType.FIELD)
  @AllArgsConstructor(access = AccessLevel.PRIVATE)
  @NoArgsConstructor
  private static class Pojos {
    @XmlElement(name = "pojo")
    private java.util.List<Pojo> values = new ArrayList<>();
  }

  @Data
  @XmlAccessorType(XmlAccessType.FIELD)
  @AllArgsConstructor(access = AccessLevel.PRIVATE)
  @NoArgsConstructor
  private static class Pojo {
    private String value = "";
  }
}

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.

@cowtowncoder
Copy link
Member

Thank you for reporting this, as per discussions.
I probably should have mentioned this earlier, but since this is related to XML, it needs to go in issue tracker of jackson-dataformat-xml.

One other thing: I don't accept code that relies on Lombok any more, since it does not work without adding Lombok jar in classpath. So those would need to be written out (may be as minimal as possible).
This should not change behavior, just makes it possible to just d/l repo, build with vanilla Maven.
(if a solution is found where pom.xml can have definitions that remove need to install anything that'd also work -- I haven't seen such a thing for Lombok)
Apologies for inconvenience.

@sir4ur0n
Copy link
Author

Hi @cowtowncoder, no problem for the non-lombok thing.
I created the ticket in FasterXML/jackson-dataformat-xml#252.

I'm closing this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants