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

Xml cannot read in Optional.empty #280

Closed
HenryYihengXu opened this issue Sep 25, 2023 · 6 comments
Closed

Xml cannot read in Optional.empty #280

HenryYihengXu opened this issue Sep 25, 2023 · 6 comments
Labels

Comments

@HenryYihengXu
Copy link

HenryYihengXu commented Sep 25, 2023

I have an object

class MyObject {
  private final int a;
  public MyObject() {
    a = 1;
  }

  // Getter
}

then another object with an optional field of the object above

class MyObject2 {
  private final Optional<MyObject> myObject;
  public MyObject2() {
    myObject = Optional.empty();
  }
  // Getter
}

I serialize MyObject2 as

MyObject2 myObject2 = new MyObject2();
XmlMapper xmlMapper = new XmlMapper();
xmlMapper.registerModule(new Jdk8Module());
xmlMapper.writeValue(new File("myObject2.xml"), myObject2);

I got

<MyObject2>
  <MyObject/>
</MyObject2>

I'm assuming a single <xxx/> denotes optional empty? Is this correct?
Assume it is correct, I deserialize it as

MyObject2 deserializedMyObject2 =
                xmlMapper.readValue(new File("myObject2.xml"), MyObject2.class);

But then I found the deserialized one is not Optional.empty. It seems to call the default constructor of MyObject so it has a = 1 in it. Do you know how should I actually deserialize Optional.empty?

@cowtowncoder
Copy link
Member

Intent with Jackson XML module is that it should be able to read what it writes: so the XML structure created with XmlMapper.writeValue(myObject2) should be readable with XmlMapper.readValue(xmlDoc, MyObject2.class).
If not, that is considered bug.

Package does not necessarily allow mapping different kinds of XML representations.

@HenryYihengXu
Copy link
Author

HenryYihengXu commented Sep 26, 2023

Then this should be a bug. I tried reading back what it writes. It's readable, but the deserialized object is different from the original one.

Here is the code:

    @Test
    void testJacksonXml2() throws IOException {
        MyObject2 myObject2 = new MyObject2();

        XmlMapper xmlMapper = new XmlMapper();
        xmlMapper.registerModule(new Jdk8Module());
        xmlMapper.writeValue(new File("test-jackson.xml"), myObject2);

        MyObject2 deserializedMyObject2 = xmlMapper.readValue(new File("test-jackson.xml"), MyObject2.class);
        xmlMapper.writeValue(new File("deserialized-test-jackson.xml"), deserializedMyObject2);
        assertThat(myObject2).isEqualTo(deserializedMyObject2);
    }

The test is failing. And I checked "test-jackson.xml" and "deserialized-test-jackson.xml". They are indeed different. The former is just

<MyObject2>
  <MyObject/>
</MyObject2>

but the later one is

<MyObject2>
  <myObject>
    <a>1</a>
  </myObject>
</MyObject2>

Could you fix this bug? I would appreciate it!

@cowtowncoder
Copy link
Member

That sounds like a bug then.

Unfortunately I doubt I have time to tackle this issue at this point. But if you or anyone else has time and interest, I can help get PR reviewed and so on.

One practical challenge here is that of testing, as the issue requires both XML module and JDK 8 datatype module -- neither of which should have dependency to the other.

But I think same problem would affect use of AtomicReference type, too, and if reproducing the issue with it, test could be added in jackson-dataformat-xml.
And fix, if any, would likely work for Optional too as their handling is very similar internally.

@HenryYihengXu
Copy link
Author

Do you have an idea on how to fix it? I looked at your OptionalDeserializer, looks like it should deserialize to Optional.empty()?

@cowtowncoder
Copy link
Member

A major problem is that empty XML element can mean either "empty" value (like "" for java.lang.String or, for POJOs, instance constructed with default constructor); OR null value.
In this case I think you get "empty" Object, then wrapped in Optional.

One way to force "null-ness" is to add xsi:nil attribute like so:

<!-- NOTE: xmlns:xsi could be declared in parent to avoid using multiple times
   -->
<MyObject2>
  <MyObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" />
</MyObject2>

@cowtowncoder
Copy link
Member

Ok, so, above xsi:nil works. But to produce it on writing, you need to enable

ToXmlGenerator.Feature.WRITE_NULLS_AS_XSI_NIL

after which things work.

I don't see a working way to solve this problem in other way but I suggest you try this.

cowtowncoder added a commit to FasterXML/jackson-integration-tests that referenced this issue Oct 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants