jackson-module-kotlin: @JacksonXmlElementWrapper doesn’t work with data classes

I want to mapping a xml using kotlin data class, my xml wrapper a list of elements in a wapper node. Xml example: <MyPojo><elements><element value="e1"/></elements></MyPojo>

Here the <element> node is a collection, wrapped in the <elements> node.

When I use the data class to map the ‘MyPojo’ and ‘element’ object and I annotated the collection property with @JacksonXmlElementWrapper and @JacksonXmlProperty it doesn’t work when I try to deserialise form xml. The exception is: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid definition for property elements (of type MyDataPojo): Could not find creator property with name ‘elements’ (known Creator properties: [element])

I use :

  • Java 1.8
  • Kotlin 1.2.31
  • Jackson: 2.9.5

I have write a test to compare the mapping using the standard class and the data class. The standard class test passed, the data class test failed.

import com.fasterxml.jackson.dataformat.xml.XmlMapper
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement
import com.fasterxml.jackson.module.kotlin.KotlinModule
import junit.framework.Assert.assertEquals
import org.junit.Test

/**
 * Mapping the Xml with standard classes
 */
@JacksonXmlRootElement(localName = "MyPojo")
class MyPojo {

  @JacksonXmlElementWrapper(localName = "elements")
  @JacksonXmlProperty(localName = "element")
  var list: List<MyElement>? = null

}

/**
 * Mapping the Xml with standard classes
 */
class MyElement {
  @JacksonXmlProperty(localName = "value", isAttribute = true)
  var value: String? = null

}

/**
 * Mapping the Xml with data classes
 */
@JacksonXmlRootElement(localName = "MyPojo")
data class MyDataPojo (
    @JacksonXmlElementWrapper(localName = "elements")
    @JacksonXmlProperty(localName = "element")
    val list
    : List<MyDataElement>
)

/**
 * Mapping the Xml with data classes
 */
data class MyDataElement (
    @JacksonXmlProperty(localName = "value", isAttribute = true)
    var value: String
)

class TextKotlinXmlWrapper {
  // xml example I want to deserialize
  val xml = """<MyPojo><elements><element value="e1"/></elements></MyPojo>"""

  val mapper = XmlMapper()
      .registerModule(KotlinModule())

  @Test
  @Throws(Exception::class)
  fun test_class() {

    // I create a pojo from the xml using the standard classes
    val pojoFromXml = mapper.readValue(xml, MyPojo::class.java)

    //I create a xml from the pojo
    val xmlFromPojo = mapper.writeValueAsString(pojoFromXml)

    // I compare the original xml with the xml generated from the pojo
    assertEquals(xml, xmlFromPojo)

  }

  @Test
  @Throws(Exception::class)
  fun test_data_class() {

    // I create a pojo from the xml using the data classes
    val pojoFromXml = mapper.readValue(xml, MyDataPojo::class.java)

    //I create a xml from the pojo
    val xmlFromPojo = mapper.writeValueAsString(pojoFromXml)

    // I compare the original xml with the xml generated from the pojo
    assertEquals(xml, xmlFromPojo)

  }

}

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 15
  • Comments: 34 (15 by maintainers)

Commits related to this issue

Most upvoted comments

Hi guys, Just in case it helps somebody else, I found a workaround by combining the things mentioned here with a property backed up with a constructor field.

This allow us to still use a data class and avoid writing getters/setters/hashcode/equals.

data class Example(private var _elements: MutableList<String>) {
    @get:JacksonXmlElementWrapper(localName = "elements")
    @get:JacksonXmlProperty(localName = "element")
    var elements
        set(value) { _elements = value }
        get() = _elements
}

It is not too elegant, but it works fine so hopefully it will help.

Thanks guys for helping me figuring it out!

FYI, from Kotlin you can specify where the annotation lands, using the get: or field: prefixes between the @ and the annotation name. So the following should work:

data class Example(
        @get:JacksonXmlElementWrapper(localName = "elements")
        @get:JacksonXmlProperty(localName = "element")
        val elements: MutableList<String>
)

so @cowtowncoder

JacksonXmlElementWrapper cannot be on a parameter therefore no property name of elements gets added to the creator parameter, and instead JacksonXmlProperty ends up on the parameter which is the wrong name. So then the mapper tries to pass in elements but the parameter appears to be property element

I have created the following test with the 2.15 branch up to date. These were created based on the tests shared in https://github.com/FasterXML/jackson-dataformat-xml/issues/517 .

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.dataformat.xml.XmlMapper
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import org.junit.Test

class GitHub153Temp {
    private val input = """
        <Request>
          <messages>
            <message>
              <text>given text</text>
            </message>
          </messages>
        </Request>
    """.trimIndent()

    data class Message(val text: String)

    data class Request1(
        @JacksonXmlElementWrapper(localName = "messages")
        @JacksonXmlProperty(localName = "message")
        val messages: List<Message>
    )

    data class Request2(val messages: List<Message>) {
        companion object {
            @JvmStatic
            @JsonCreator
            fun creator(
                @JacksonXmlElementWrapper(localName = "messages")
                @JacksonXmlProperty(localName = "message")
                messages: List<Message>
            ) = Request2(messages)
        }
    }

    @Test
    fun onlyJavaMapper1() {
        val mapper = XmlMapper()
        val result = mapper.readValue<Request1>(input)
        println(result)
    }

    @Test
    fun withKotlinMapper1() {
        val mapper = XmlMapper().registerKotlinModule()
        val result = mapper.readValue<Request1>(input)
        println(result)
    }

    @Test
    fun onlyJavaMapper2() {
        val mapper = XmlMapper()
        val result = mapper.readValue<Request2>(input)
        println(result)
    }

    @Test
    fun withKotlinMapper2() {
        val mapper = XmlMapper().registerKotlinModule()
        val result = mapper.readValue<Request2>(input)
        println(result)
    }
}
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid definition for property 'messages' (of type `com.fasterxml.jackson.module.kotlin.test.Temp$Request1`): Could not find creator property with name 'messages' (known Creator properties: [message])
 at [Source: (StringReader); line: 1, column: 1]

All of these tests fail with the same stack trace as the original, with or without kotlin-module. Therefore, we can assume that this is not a problem with Kotlin or kotlin-module, but simply that the JacksonXmlElementWrapper does not work for the creator parameter.

If you have evidence that Kotlin or kotlin-module is the cause, please submit a new issue.

For all the exhausted gogglers out there who try to fix this issue: at this day Jackson XML de-serialization is still bugged when paired with Kotlin Data-Classes => do yourself the favor and use classes instead.

While the solution presented here works well for serialization, I’ve found that in jackson 2.9.8 it breaks for de-serialization due to the KotlinValueInstantiator trying to find the _elements parameter in the xml. The solution is to simply give the _elements parameter a default value:

data class Example(private var _elements: MutableList<String> = mutableListOf())

De-serializes properly with Json too, even with polymorphic elements.