I have the following code. It is in Kotlin, which may be relevant, but I don't think so:
class WordCounts private @JsonCreator constructor(
@JsonProperty("sourceToken") val sourceToken: ValueToken<List<String>>,
@JsonProperty("resultId") resultId: ValueIdGroup<Int>,
@JsonProperty("wordMap") val wordMap: MutableMap<String, Int>) : LearningTransform {
// bunch more code here that's not relevant
}
And I have a serialized object that looks like this:
{"sourceToken":{"id":{"name":"words","clazz":"java.util.List"}},"resultId":{"prefix":"wordCounts","clazz":"java.lang.Integer"},"wordMap":{"foo":0,"bar":1,"baz":2}}
When I try to deserialize it I get:
com.fasterxml.jackson.databind.JsonMappingException: Argument #1 of constructor [constructor for com.analyticspot.ml.framework.testutils.WordCounts, annotations: {interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}] has no property name annotation; must have name when multiple-parameter constructor annotated as Creator
at [Source: {"sourceToken":{"id":{"name":"words","clazz":"java.util.List"}},"resultId":{"prefix":"wordCounts","clazz":"java.lang.Integer"},"wordMap":{"foo":0,"bar":1,"baz":2}}; line: 1, column: 1]
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:305)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:268)
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:476)
at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:3899)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3794)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2842)
at com.analyticspot.ml.framework.serialization.GraphSerDeserTest.testTokenGroupsSerialize(GraphSerDeserTest.kt:166)
Which seems odd since there is a @JsonProperty annotation on all the values.
Note that ValueIdGroup serializes and deserializes fine by itself (I have unit tests for that).
I also tried adding a static factory method annotated with @JsonCreate and the @JsonProperty annotations:
class WordCounts private constructor(
val sourceToken: ValueToken<List<String>>,
resultId: ValueIdGroup<Int>,
val wordMap: MutableMap<String, Int>) : LearningTransform {
companion object {
private val log = LoggerFactory.getLogger(WordCounts::class.java)
@JvmStatic @JsonCreator
fun createFromSerialized(
@JsonProperty("sourceToken") sourceToken: ValueToken<List<String>>,
@JsonProperty("resultId") resultId: ValueIdGroup<Int>,
@JsonProperty("wordMap") wordMap: MutableMap<String, Int>): WordCounts {
return WordCounts(sourceToken, resultId, wordMap)
}
}
// other irrelevant code
}
but the result is the same. In desperation I also tried rewriting ValueIdGroup a few ways thinking maybe that was somehow confusing it and the error message was just wrong but that didn't help either.
I've reproduced this with Jackson 2.7.0, 2.7.4, and 2.8.4 with and without com.fasterxml.jackson.module:jackson-module-kotlin:2.8.4
Am very stuck. Any help would be much appreciated.
I found a workaround, but I'm pretty sure it shouldn't be necessary:
@JsonDeserialize(builder = WordCounts.Builder::class)
class WordCounts private constructor(
val sourceToken: ValueToken<List<String>>,
resultId: ValueIdGroup<Int>,
val wordMap: MutableMap<String, Int>) : LearningTransform {
@JsonPOJOBuilder(withPrefix = "set")
class Builder() {
lateinit var sourceToken: ValueToken<List<String>>
lateinit var resultId: ValueIdGroup<Int>
lateinit var wordMap: MutableMap<String, Int>
fun build(): WordCounts = WordCounts(sourceToken, resultId, wordMap)
}
// other code..
}
@oliverdain since there are no known issues in this area, I would suspect it is closely tied to Kotlin module's handling. One possibility could be that Kotlin has a hidden parameter (maybe similar to hidden this that is used by non-static Java inner classes); or that reflection somehow either sees additional parameters.
On the other hand there is always possibility of something going wrong in jackson-databind. If it was possible to have Java-only reproduction, I could investigate that easily.
@apatrida what do you think?
Unfortunately I have no way of reproducing this problem at this point. I would recommend upgrade to the latest versions (2.8.7) of modules since there have been some introspection fixes, but not sure anything specifically relevant here.
I'm using 2.9.1 and I have the same problem.
@Test
fun jsonTest(){
val objectMapper = ObjectMapper()
val dataView = DataView("someId", MonthDay(1, true))
println(objectMapper.writeValueAsString(dataView))
}
And those are the data classes
data class DataView(val id: String,
@JsonProperty("dayOfMonth") val monthDay: MonthDay)
data class MonthDay(val day: Int, @JsonProperty("asfasdf") val isSunny: Boolean)
This is the output
{"id":"someId","monthDay":{"day":1,"sunny":true}}
It only works if I create ObjectMapper like this:
val objectMapper = ObjectMapper().registerKotlinModule()
But still it doesn't work with booleans
It works if I use @field:JsonProperty like this:
data class DataView(val id: String,
@field:JsonProperty("dayOfMonth") val monthDay: MonthDay)
data class MonthDay(val day: Int, @field:JsonProperty("asfasdf") val isSunny: Boolean)
I also observed the same behaviour but I'm almost certain the issue is with Kotlin's data class.
I had properties starting with is eg isAlive and the prefix is would always be trimmed during marshalling resulting in JSON property named alive and the discrepancy in field names would then of course make Jackson fail during unmarshalling. @JsonProperty("isAlive") seemed to have no effect but @field:JsonProperty("isAlive") like @fruedaCode suggested did work.
Most helpful comment
It works if I use @field:JsonProperty like this: