jackson-module-kotlin icon indicating copy to clipboard operation
jackson-module-kotlin copied to clipboard

Unable to deserialize missing XML tag to null

Open henrik242 opened this issue 4 years ago • 2 comments

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 avatar Jun 07 '21 12:06 henrik242

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 avatar Jun 07 '21 15:06 cowtowncoder

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.

henrik242 avatar Jun 07 '21 16:06 henrik242

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 avatar Oct 15 '23 12:10 k163377

Related problem from 2.12.3 and onwards: https://github.com/FasterXML/jackson-module-kotlin/issues/721

henrik242 avatar Nov 01 '23 14:11 henrik242