jackson-module-kotlin: InvalidDefinitionException in case of deserialization xml to data class with @JacksonXmlText annotation

Data class example:

@JacksonXmlRootElement(localName = "sms")
@JsonIgnoreProperties(ignoreUnknown = true)
data class Sms(
        @set:JacksonXmlProperty(localName = "Phone", isAttribute = true)
        var phone: String?,

        @JacksonXmlText
        var text: String? = ""
)

Corresponding xml:

<sms Phone="435242423412" Id="43234324">Lorem ipsum</sms>

Deserialization causes such output:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid definition for property `` (of type `proton72.github.com.Sms`): Could not find creator property with name '' (known Creator properties: [Phone, text])
 at [Source: (StringReader); line: 1, column: 1]
	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:62)
	at com.fasterxml.jackson.databind.DeserializationContext.reportBadPropertyDefinition(DeserializationContext.java:1446)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.addBeanProps(BeanDeserializerFactory.java:567)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:227)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:137)
	at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:411)
	at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:349)
	at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)
	at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
	at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
	at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:477)
	at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:4178)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3997)

Reproduced on jackson-dataformat-xml and jackson-module-kotlin 2.9.4 both.

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 7
  • Comments: 16 (8 by maintainers)

Commits related to this issue

Most upvoted comments

Outdated, I recommend to use setXMLTextElementName.

Based on the above discovery here’s another workaround (instead of using JsonNode):

data class Attribute(
	@JacksonXmlProperty(isAttribute = true)
	val code: String
) {
	@JacksonXmlText
	lateinit var title: String private set

	override fun toString() = "$code->$title"
}
Feed(attributes=[private->Private Event, public->Public Event])

Note: this is not a solution as the field will be writable (var, private set helps a bit, but still), will have no construct-time validation that the field is set (lateinit), and the field will not play in data class operations like equals, toString, and others (it’s not in the primary constructor)!

I just found someone on kotlin slack (named Stephan Schroeder) providing a solution :

You need a mapper like this :

XmlMapper(JacksonXmlModule()
        .apply {
            setXMLTextElementName("text")
        })

and annotate your field like this:

data class Attribute(
	@JacksonXmlProperty(isAttribute = true)
	val code: String,

        @JsonProperty("text")
	@JacksonXmlText
	val title: String?
)

This works for me with jackson 2.10.1. Slack user claims it works with jackson 2.9.9

Genius, it works for me too! Thank you very much for sharing @ctruchi! Note @ctruchi @JacksonXmlText doesn’t have an effect with setXMLTextElementName when deserializing only. However it is required for serialization.


Following @ctruchi’s example this also works, with the benefit of not mixing XML and JSON annotations:

XmlMapper(JacksonXmlModule().apply {
	setXMLTextElementName("innerText")
})

data class Attribute(
	@JacksonXmlProperty(isAttribute = true)
	val code: String,

        @JacksonXmlProperty(localName = "innerText") // for deserialization
        @JacksonXmlText // for serialization
	val title: String
)

@cowtowncoder To move this a bit forward…

Here’s a base repro:

package test
fun main(args: Array<String>) {
	val mapper = XmlMapper().apply {
		registerModule(KotlinModule())
		configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
	}

	val feed = mapper.readValue<Feed>(
		"""
		<feed>
		    <attributes>
		        <attribute code="private">Private Event</attribute>
		        <attribute code="public">Public Event</attribute>
		    </attributes>
		</feed>
		""".trimIndent()
	)
	println(feed)
}

@JacksonXmlRootElement(localName = "feed")
data class Feed(
	@JacksonXmlElementWrapper(localName = "attributes")
	val attributes: List<Attribute>
)

data class Attribute(
	@JacksonXmlProperty(isAttribute = true)
	val code: String,

	@JacksonXmlText
	val title: String?
)
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid definition for property `` (of type `test.Attribute`): Could not find creator property with name '' (known Creator properties: [code, title])
 at [Source: (StringReader); line: 1, column: 1]
	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:62)
	at com.fasterxml.jackson.databind.DeserializationContext.reportBadPropertyDefinition(DeserializationContext.java:1446)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.addBeanProps(BeanDeserializerFactory.java:567)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:227)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:137)

removing @JacksonXmlText works as expected, but obviously doesn’t deserialize the text:

Feed(attributes=[Attribute(code=private, title=null), Attribute(code=public, title=null)])

Commenting/removing the Kotlin Attribute class and using this instead works:

package test;
public class Attribute {
	@JacksonXmlProperty(isAttribute = true)
	public String code;

	@JacksonXmlText
	public String title;

	@Override public String toString() { return String.format("Attribute(%s, %s)", code, title); }
}
Feed(attributes=[Attribute(private, Private Event), Attribute(public, Public Event)])

Getting a bit more close to Kotlin data classes fails the same way as Kotlin:

public class Attribute {

	@JacksonXmlProperty(isAttribute = true)
	private final String code;

	@JacksonXmlText
	private final String title;

	public Attribute(
			@JacksonXmlProperty(localName = "code") String code,
			@JacksonXmlProperty(localName = "title") String title) {
		this.code = code;
		this.title = title;
	}

	@Override public String toString() {
		return String.format("Attribute(%s, %s)", code, title);
	}
}
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid definition for property `` (of type `test.Attribute`): Could not find creator property with name '' (known Creator properties: [code, title])
 at [Source: (StringReader); line: 1, column: 1]
	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:62)
	at com.fasterxml.jackson.databind.DeserializationContext.reportBadPropertyDefinition(DeserializationContext.java:1446)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.addBeanProps(BeanDeserializerFactory.java:567)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:227)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:137)

Note: removing registerModule(KotlinModule()) makes no difference.

If we solve the Java version to work (maybe the annotations are wrong), we could figure out how to do the same in Kotlin.

I was using the innerText workaround mentioned above but after updating to a newer version of the library, this no longer worked for me either.

Using version 2.12.6 of jackson-dataformat-xml and jackson-module-kotlin I had to move the property out of the constructor (and make it a nullable var), as a result had to stop using a data class as these require at least one constructor parameter. Using the @JacksonXmlText meant I didn’t need the innerText anymore. This was for deserializing only as this was all I required.

For this XML:

<statusResponse>3</statusResponse>

The following worked for me:

@JacksonXmlRootElement(localName = "statusResponse")
class ResponseChangeStatus(): {
        @JacksonXmlText()
        var status : Short? = null
}

I was still able to use the builder method for creating the XmlMapper, without the need for nameForTextElement("innerText") .

XmlMapper.builder()
     .build()
     .registerKotlinModule()

Haven’t tried this on 2.13.x and above versions yet.