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

defaultUseWrapper setting does not work when xsi:nil is used as attribute #372

Closed
pablow91 opened this issue Nov 8, 2019 · 3 comments
Closed
Milestone

Comments

@pablow91
Copy link

pablow91 commented Nov 8, 2019

There is an issue with deserilalizing collecions when one of the element contains xsi:nil attribuite and the default use wrapper is set to false.

Code for reproducing the issue:

fun main() {
    xmlMapper.readValue<Root>(xml)
}

val xmlMapper = XmlMapper()
    .setDefaultUseWrapper(false)
    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
    .registerKotlinModule()
    .registerModule(JaxbAnnotationModule()) as XmlMapper

private const val xml = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Root>
    <Item>
        <One xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">one</One>
        <Two>two</Two>
    </Item>
    <Item>
        <One>one1</One>
        <Two>two2</Two>
    </Item>
</Root>"""

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Root")
class Root {
    @XmlElement(name = "Item")
    lateinit var items: List<Item>
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Item")
class Item {
    @XmlElement(name = "One")
    lateinit var one: SubItem
    @XmlElement(name = "Two")
    lateinit var two: SubItem
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "SubItem")
class SubItem {
    @XmlValue
    lateinit var value: String
}

This code works in 2.9.10, but after upgrading to the 2.10.0 the code returns the exception:
Exception in thread "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `maven.Item` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('two')

@scprek
Copy link

scprek commented Dec 3, 2019

I'm not sure if my issues is 100% related to this one, as it's more general than defaultUseWrapper but posting here first.

As @pablow91 mentioned, it "works" with 2.9.10 meaning the xsi:nil is accepted as Empty string, but at least the parser doesn't crash or mess up subsequent values

@cowtowncoder I see that test cases where added #366 , but is there a corresponding issue? Is this the issue? Saw the note about not knowing when to look at straightening the xsi attributes out.

Here's my write up to expand on what others have already mentioned

It appears that when the xsi:nil="true" appears it can either cause an exception or set all remaining values as null when parsing subsequent elements. It looks like the original fix #345 did not have the test cases above and it appears that the nil was always tested alone. 4938036#diff-830fdec9bd70e14d6473d0dcf37138f6, but combination tests added after #366 as mentioned above

I'm not the best with XML and maybe I'm misunderstanding xsi:nil="true". Rather than try to word the issues perfectly, I wrote a small test program with 4 different XML scenarios.

Setup

  • Java 8
  • Jackson:
    • jackson-databind (2.10.1)
    • jackson-dataformat-xml (2.10.1)

Test Scenario Results

  1. Testing non nil XML
<Test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <subObj1>
       <x>test-x</x>
       <y>test-y</y>
   </subObj1>
   <subobj2>
       <z>test-z</z>
   </subobj2>
</Test>
Result Obj: TopObj{subObj1=SubObj1{x='test-x', y='test-y'}, subobj2=SubObj{z='test-z'}}
  1. Testing nil at end of SubObj1, proving things work but all following values are null (subobj2)
<Test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <subObj1>
       <x>test-x</x>
       <y xsi:nil="true" />
   </subObj1>
   <subobj2>
       <z>test-z</z>
   </subobj2>
</Test>
Result Obj: TopObj{subObj1=SubObj1{x='test-x', y='null'}, subobj2=null}
  1. Testing nil at end of SubObj2, proving things work fully when nil is only at the end
<Test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <subObj1>
       <x>test-x</x>
       <y>test-y</y>
   </subObj1>
   <subobj2>
       <z xsi:nil="true" />
   </subobj2>
</Test>
Result Obj: TopObj{subObj1=SubObj1{x='test-x', y='test-y'}, subobj2=SubObj{z='null'}}
  1. Testing nil at start of object, proving parsing crashes. Claims the next field is "Unrecognized"
<Test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <subObj1>
       <x xsi:nil="true" />
       <y>test-y</y>
   </subObj1>
   <subobj2>
       <z>test-z</z>
   </subobj2>
</Test>
Result: Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "y" (class com.test.TopObj), not marked as ignorable (2 known properties: "subObj1", "subobj2"])
 at [Source: (StringReader); line: 4, column: 21] (through reference chain: com.test.TopObj["y"])
	at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:61)
	at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:840)
	at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1179)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1592)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1570)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:294)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4202)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3205)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3173)
	at com.test.NilTester.testXML(NilTester.java:59)
	at com.test.NilTester.main(NilTester.java:55)

Test Program

package com.test;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;

class TopObj {
    public SubObj1 subObj1;
    public SubObj2 subobj2;

    @Override
    public String toString() {
        return "TopObj{" +
                "subObj1=" + subObj1 +
                ", subobj2=" + subobj2 +
                '}';
    }
}

class SubObj1 {
    public String x;
    public String y;

    @Override
    public String
    toString() {
        return "SubObj1{" +
                "x='" + x + '\'' +
                ", y='" + y + '\'' +
                '}';
    }
}

class SubObj2 {
    public String z;

    @Override
    public String toString() {
        return "SubObj{" +
                "z='" + z + '\'' +
                '}';
    }
}

public class NilTester {

    public static void main(String[] args) throws Exception {
        System.out.println("1. Testing non nil XML");
        testXML(XML_SUCCESS_DESER);
        System.out.println("2. Testing nil at end of SubObj1, proving things work but all following values are null (subobj2)");
        testXML(XML_MOVE_NIL_TO_END_OBJ_ONE_DESER);
        System.out.println("3. Testing nil at end of SubObj2, proving things work fully when nil is only at the end");
        testXML(XML_MOVE_NIL_TO_END_OBJ_TWO_DESER);
        System.out.println("4. Testing nil at start of object, proving parsing crashes. Claims the next field is \"Unrecognized\"");
        testXML(XML_FAIL_DESER);
    }

    private static void testXML(String xml) throws Exception {
        System.out.println(xml);        
        TopObj topObj = mapper.readValue(xml, TopObj.class);
        System.out.println("Result Obj: " + topObj.toString() + "\n");
    }


    /**
     * No nils
     */
    private static String XML_SUCCESS_DESER = String.join("\n",
            "<Test xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">",
            "   <subObj1>",
            "       <x>test-x</x>",
            "       <y>test-y</y>",
            "   </subObj1>",
            "   <subobj2>",
            "       <z>test-z</z>",
            "   </subobj2>",
            "</Test>"
    );

    /**
     * Move {@code xsi:nil="true"} to last element in {@link SubObj1}, it works, but all following members in
     * {@link SubObj2} are null
     */
    private static String XML_MOVE_NIL_TO_END_OBJ_ONE_DESER = String.join("\n",
            "<Test xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">",
            "   <subObj1>",
            "       <x>test-x</x>",
            "       <y xsi:nil=\"true\" />",
            "   </subObj1>",
            "   <subobj2>",
            "       <z>test-z</z>",
            "   </subobj2>",
            "</Test>"
    );

    /**
     * Move {@code xsi:nil="true"} to last element in {@link SubObj2}, it works as it's the last element to parse
     */
    private static String XML_MOVE_NIL_TO_END_OBJ_TWO_DESER = String.join("\n",
            "<Test xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">",
            "   <subObj1>",
            "       <x>test-x</x>",
            "       <y>test-y</y>",
            "   </subObj1>",
            "   <subobj2>",
            "       <z xsi:nil=\"true\" />",
            "   </subobj2>",
            "</Test>"
    );

    /**
     * Note {@code xsi:nil="true"} in {@link SubObj1#x}, crashing the deserialization of {@link SubObj1#y}
     */
    private static String XML_FAIL_DESER = String.join("\n",
            "<Test xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">",
            "   <subObj1>",
            "       <x xsi:nil=\"true\" />",
            "       <y>test-y</y>",
            "   </subObj1>",
            "   <subobj2>",
            "       <z>test-z</z>",
            "   </subobj2>",
            "</Test>"
    );

    private static ObjectMapper mapper = new XmlMapper();
}

@pablow91
Copy link
Author

Looks like my issue no longer occurs in version 2.10.2

@cowtowncoder
Copy link
Member

@pablow91 Thank you for verifying this. I think fix was one listed for #366 / #378.

@cowtowncoder cowtowncoder added this to the 2.10.2 milestone Jan 20, 2020
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

3 participants