-
-
Notifications
You must be signed in to change notification settings - Fork 143
Description
Jackson version 2.8.9, 2.8.6:
Say, the following case class is defined:
case class Result(data: Seq[Int] = Seq.empty) { def this() = this(Seq(1)) }
Then, if we write the following test:
import com.fasterxml.jackson.annotation.JsonInclude.Include
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import org.scalatest.WordSpec
class DeserilizationTest extends WordSpec {
"Object mapper" when {
val Mapper = new ObjectMapper() with ScalaObjectMapper
val module = new SimpleModule
Mapper
.registerModule(module)
.registerModule(DefaultScalaModule)
.setSerializationInclusion(Include.NON_EMPTY)
"deserializing case class with a default constructor" in {
val result = Result()
val serialized = Mapper.writeValueAsString(result)
val deserialized = Mapper.readValue(serialized.getBytes, classOf[Result])
assert(deserialized.data === Seq(1))
}
}
}
case class Result(data: Seq[Int] = Seq.empty) {
def this() = this(Seq(1))
}
It will fail in a few runs because deserialized.data would equal to null.
The root cause is the:
com.fasterxml.jackson.module.scala.introspect.BeanIntrospector#def findConstructorParam(c: Class[_], name: String): Option[ConstructorParameter]
This method resolves
val primaryConstructor = c.getConstructors.headOption
If primaryConstructor
is resolved to a default constructor with no arguments, then findConstructorParam will eventually return None. If None is returned, BeanDeserializerBase._vanillaProcessing
will eventually be set to true, this will make Jackson call the default constructor of Result.
But, if the primaryConstructor
is resolved to the constructor with the argument, then findConstructorParam will return Some(), which eventually leads Jackson to ignore the default constructor.
The primaryConstructor
resolution should be changed to something like:
c.getConstructors.find(findADefaultConstructor).orElse(c.getConstructors.headOption)
Note:
The issue of not calling the default constructor can be eliminated by annotating it with @JsonCreator as follows:
case class Result(data: Seq[Int] = Seq.empty) { @JsonCreator def this() = this(Seq(1)) }