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

Unable to deserialize missing XML tag to null #461

Closed
henrik242 opened this issue Jun 7, 2021 · 4 comments
Closed

Unable to deserialize missing XML tag to null #461

henrik242 opened this issue Jun 7, 2021 · 4 comments

Comments

@henrik242
Copy link

henrik242 commented Jun 7, 2021

I have a simple parser and data structure (Kotlin):

object XmlTool {
    val xmlIn = XMLInputFactory.newInstance()
    val factory = XmlFactory(xmlIn)
    val xmlModule = JacksonXmlModule()
    val mapper = XmlMapper(factory, xmlModule)
        .registerKotlinModule()
        .configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true) // Doesn't make any difference

    @JvmStatic
    fun parseRoot(xml: String?): Root = mapper.readValue(xml, Root::class.java)

    @JvmStatic
    fun parseOuter(xml: String?): Outer = mapper.readValue(xml, Outer::class.java)

    data class Inner(val code: String?)
    data class Outer(val inner: Inner?)
    data class Root(val outer: Outer?)
}

.. and a test (Java):

class NoStringArgumentTest {

    @Test
    void no_string_argument() {
        String xml = "<root>" +
                     "   <outer>" +
                     "      <inner code=\"578\" />" +
                     "   </outer>" +
                     "</root>";

        Root product = XmlTool.parseRoot(xml);

        assertEquals(new Root(new Outer(new Inner("578"))), product); // SUCCESS
    }

    @Test
    void no_string_argument2() {
        String xml = "<outer>" +
                     "</outer>";

        Outer product = XmlTool.parseOuter(xml);

        assertEquals(new Outer(null), product); // SUCCESS in Jackson 2.11.3, but FAIL in 2.12.3
    }

    @Test
    void no_string_argument3() {
        String xml = "<root>" +
                     "   <outer>" +
                     "   </outer>" +
                     "</root>";

        Root product = XmlTool.parseRoot(xml);

        assertEquals(new Root(new Outer(new Inner(null))), product); // Always FAILS
    }
}

The last test fails with the following exception:

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `jackson.xml.XmlTool$Outer` (although at least one Creator exists): no default no-arguments constructor found
 at [Source: (StringReader); line: 1, column: 8]

	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
	at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1588)
	at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1213)
	at com.fasterxml.jackson.databind.deser.ValueInstantiator.createUsingDefault(ValueInstantiator.java:248)
	at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createUsingDefault(StdValueInstantiator.java:275)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.getEmptyValue(BeanDeserializerBase.java:1042)
	at com.fasterxml.jackson.databind.deser.std.StdDeserializer._deserializeFromEmptyString(StdDeserializer.java:322)
	at com.fasterxml.jackson.databind.deser.std.StdDeserializer._deserializeFromString(StdDeserializer.java:270)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1495)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:207)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:197)
	at com.fasterxml.jackson.dataformat.xml.deser.XmlDeserializationContext.readRootValue(XmlDeserializationContext.java:91)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4593)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3548)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3516)
	at jackson.xml.XmlTool.parseOuter(XmlTool.kt:22)
	at jackson.xml.NoStringArgumentTest.no_string_argument2(NoStringArgumentTest.java:30)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:675)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:125)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:132)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:124)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:74)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:104)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:62)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:43)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:35)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:202)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:198)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)

What do I need to change to make this test pass?

I have created a test project at https://github.com/henrik242/jackson-xml-problem/tree/no-string-argument

@henrik242 henrik242 changed the title Unable to deserialize missing tag as null Unable to deserialize missing tag to null Jun 7, 2021
@cowtowncoder
Copy link
Member

I think this might be Kotlin-specific, so moving.

One question: is this with Jackson 2.12(.3) or earlier? 2.12 added a few improvements to null handling, although I suspect this issue may be wrt Delegating-vs-Properties-based creators.

@cowtowncoder cowtowncoder changed the title Unable to deserialize missing tag to null Unable to deserialize missing XML tag to null Jun 7, 2021
@cowtowncoder cowtowncoder transferred this issue from FasterXML/jackson-dataformat-xml Jun 7, 2021
@henrik242
Copy link
Author

henrik242 commented Jun 7, 2021

Thanks! This is with 2.12.3. Also, one of the test cases (no_string_argument2()) works with 2.11.3, but fails in 2.12.3.

@k163377
Copy link
Contributor

k163377 commented Oct 15, 2023

Tried on branch 2.16 but could not reproduce, so it is closed.

import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule
import com.fasterxml.jackson.dataformat.xml.XmlFactory
import com.fasterxml.jackson.dataformat.xml.XmlMapper
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import com.fasterxml.jackson.module.kotlin.test.github.XmlTool.Inner
import com.fasterxml.jackson.module.kotlin.test.github.XmlTool.Outer
import com.fasterxml.jackson.module.kotlin.test.github.XmlTool.Root
import com.fasterxml.jackson.module.kotlin.test.github.XmlTool.parseOuter
import com.fasterxml.jackson.module.kotlin.test.github.XmlTool.parseRoot
import junit.framework.TestCase.assertEquals
import javax.xml.stream.XMLInputFactory
import kotlin.test.Test

object XmlTool {
    val xmlIn = XMLInputFactory.newInstance()
    val factory = XmlFactory(xmlIn)
    val xmlModule = JacksonXmlModule()
    val mapper = XmlMapper(factory, xmlModule)
        .registerKotlinModule()
        .configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true) // Doesn't make any difference

    @JvmStatic
    fun parseRoot(xml: String?): Root = mapper.readValue(xml, Root::class.java)

    @JvmStatic
    fun parseOuter(xml: String?): Outer = mapper.readValue(xml, Outer::class.java)

    data class Inner(val code: String?)
    data class Outer(val inner: Inner?)
    data class Root(val outer: Outer?)
}

class Github461 {
    @Test
    fun no_string_argument() {
        val xml = "<root>" +
                "   <outer>" +
                "      <inner code=\"578\" />" +
                "   </outer>" +
                "</root>"
        val product = parseRoot(xml)
        assertEquals(Root(Outer(Inner("578"))), product) // SUCCESS
    }

    @Test
    fun no_string_argument2() {
        val xml = "<outer>" +
                "</outer>"
        val product = parseOuter(xml)
        assertEquals(Outer(null), product) // SUCCESS in Jackson 2.11.3, but FAIL in 2.12.3
    }

    @Test
    fun no_string_argument3() {
        val xml = "<root>" +
                "   <outer>" +
                "   </outer>" +
                "</root>"
        val product = parseRoot(xml)
        assertEquals(Root(Outer(Inner(null))), product) // Always FAILS
    }
}

@k163377 k163377 closed this as completed Oct 15, 2023
@henrik242
Copy link
Author

henrik242 commented Nov 1, 2023

Related problem from 2.12.3 and onwards: #721

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