-
-
Notifications
You must be signed in to change notification settings - Fork 143
FAQ
TL;DR: Add one of the reflection based extensions (see Related Projects). Or use @JsonDeserialize(contentAs = classOf[java.lang.Integer])
or @JsonDeserialize(contentAs = classOf[Int])
on the offending member.
Scala, unlike Java, supports using primitive types as type arguments to generic classes. However, this is not supported by the JVM; there is no way to represent java.lang.List<int>
, as an example. Prior to Scala 2.9, the compiler would represent Option[Int]
as Option[java.lang.Integer]
to the JVM, which would appear to naturally fall out of the requirement to use reference types as type parameters.
However, this led to significant problems, for example, implementing bridge methods for traits. The solution, for the time being, is that all primitive type parameters are represented as Object
to the JVM. The Scala compiler uses the ScalaSignature
annotation to determine what the Scala type needs to be in the cases where it matters.
Enter Jackson, which at its core is a Java library. The Scala module has informed Jackson that Option
is effectively a container type, but it relies on Java reflection to determined the contained type, and comes up with Object
. Jackson has rules for dealing with this situation, which dictate that the dynamic type of Object
values should be the closest natural Java type for the value: java.lang.String
for strings, java.lang.Integer
, java.lang.Long
, or java.math.BigInteger
for whole numbers, depending on what size the number fits into, and similarly java.lang.Float
, java.lang.Double
, or java.lang.BigDecimal
for floating point values.
The Scala compiler doesn't know what evil has been done in Java-land. It "knows" the value is a primitive int
even if the JVM says Object
, so it attempts to cast the value to the relevant boxed type and unbox it. If the primitive type matches the type selected by Jackson, it works; otherwise the actual type protests such treatment in the form of a ClassCastException
.
See Related Projects. These extensions work with jackson-module-scala v2.13 and above.
These alternatives will work without issue.
- Option[BigInt]
- Option[java.lang.Integer]
The current workaround for this use case is to add the @JsonDeserialize
annotation to the member being targeted (Java examples). Specifically, this annotation has a set of parameters that can be used for different situations:
-
contentAs
for collections or map values -
keyAs
for Map keys
Examples of how to use this annotation can be found in the tests directory.
Baeldung have some good Jackson docs. The docs focus on Java use cases but they should be usable for Scala use cases. They have one about registering a custom deserializer.
jackson-module-scala 2.13.1 adds ScalaAnnotationIntrospectorModule.registerReferencedType
- that can be used if you can't annotate the class. (jackson-module-scala 2.13.0 had ScalaAnnotationIntrospector.registerReferencedType
but this is now deprecated in favour of the newer method.
case class OptionLong(valueLong: Option[Long])
case class WrappedOptionLong(text: String, wrappedLong: OptionLong)
ScalaAnnotationIntrospectorModule.registerReferencedValueType(classOf[OptionLong], "valueLong", classOf[Long])
If you want to deserialize JSON to OptionLong or WrappedOptionLong, then this registerReferencedValueType
will inform jackson-module-scala that valueLong is an optional Long - because jackson-module-scala currently can't infer this.
This the mechanism used under the hood by jackson-scala-reflect-extensions and jackson-scala3-reflection-extensions.