Skip to content

Commit 5bd3378

Browse files
committed
Migrate Option Deserializer to TypeReference.
1 parent c2d19ad commit 5bd3378

File tree

4 files changed

+72
-59
lines changed

4 files changed

+72
-59
lines changed

build.sbt

+4-2
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,15 @@ scalacOptions ++= (
3636
}
3737
)
3838

39-
val jacksonVersion = "2.7.4-SNAPSHOT"
39+
val jacksonVersion = "2.7.3"
40+
41+
val jacksonSnapshot = "2.7.4-SNAPSHOT"
4042

4143
libraryDependencies ++= Seq(
4244
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
4345
"com.fasterxml.jackson.core" % "jackson-core" % jacksonVersion,
4446
"com.fasterxml.jackson.core" % "jackson-annotations" % jacksonVersion,
45-
"com.fasterxml.jackson.core" % "jackson-databind" % jacksonVersion,
47+
"com.fasterxml.jackson.core" % "jackson-databind" % jacksonSnapshot,
4648
"com.fasterxml.jackson.module" % "jackson-module-paranamer" % jacksonVersion,
4749
// test dependencies
4850
"com.fasterxml.jackson.datatype" % "jackson-datatype-joda" % jacksonVersion % "test",

src/main/scala/com/fasterxml/jackson/module/scala/deser/OptionDeserializerModule.scala

+66-54
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,95 @@
11
package com.fasterxml.jackson.module.scala.deser
22

3-
import com.fasterxml.jackson.core.JsonParser
3+
import com.fasterxml.jackson.core.{JsonToken, JsonParser}
44
import com.fasterxml.jackson.databind._
5-
import com.fasterxml.jackson.databind.`type`.CollectionLikeType
5+
import com.fasterxml.jackson.databind.`type`.{ReferenceType, TypeFactory, CollectionLikeType}
66
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
77
import com.fasterxml.jackson.databind.deser.{ContextualDeserializer, Deserializers}
88
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer
99
import com.fasterxml.jackson.module.scala.modifiers.OptionTypeModifierModule
1010

11-
private class OptionDeserializer(elementType: JavaType,
12-
valueTypeDeser: Option[TypeDeserializer],
13-
beanProperty: Option[BeanProperty],
14-
elementDeser: Option[JsonDeserializer[_]])
15-
extends StdDeserializer[Option[AnyRef]](classOf[Option[AnyRef]]) with ContextualDeserializer {
16-
17-
override def createContextual(ctxt: DeserializationContext, property: BeanProperty): JsonDeserializer[_] = {
18-
val typeDeser = valueTypeDeser.map(_.forProperty(property))
19-
val deser: Option[JsonDeserializer[_]] =
20-
(for {
21-
p <- Option(property)
22-
m <- Option(p.getMember)
23-
deserDef <- Option(ctxt.getAnnotationIntrospector.findContentDeserializer(m))
24-
} yield ctxt.deserializerInstance(m, deserDef)).orElse(elementDeser)
25-
val deser1: Option[JsonDeserializer[_]] = Option(findConvertingContentDeserializer(ctxt, property, deser.orNull))
26-
val deser2: Option[JsonDeserializer[_]] = if (deser1.isEmpty) {
27-
if (hasContentTypeAnnotation(ctxt, property)) {
28-
Option(ctxt.findContextualValueDeserializer(elementType, property))
29-
} else {
30-
deser1
31-
}
32-
} else {
33-
Option(ctxt.handleSecondaryContextualization(deser1.get, property, elementType))
11+
private class OptionDeserializer(fullType: JavaType,
12+
valueTypeDeserializer: Option[TypeDeserializer],
13+
valueDeserializer: Option[JsonDeserializer[AnyRef]],
14+
beanProperty: Option[BeanProperty] = None)
15+
extends StdDeserializer[Option[AnyRef]](fullType) with ContextualDeserializer {
16+
17+
override def getValueType: JavaType = fullType
18+
19+
override def getNullValue: Option[AnyRef] = None
20+
21+
private[this] def withResolved(fullType: JavaType,
22+
typeDeser: Option[TypeDeserializer],
23+
valueDeser: Option[JsonDeserializer[_]],
24+
beanProperty: Option[BeanProperty]): OptionDeserializer = {
25+
if (fullType == this.fullType &&
26+
typeDeser == this.valueTypeDeserializer &&
27+
valueDeser == this.valueDeserializer &&
28+
beanProperty == this.beanProperty) {
29+
return this
3430
}
35-
if (deser2 != elementDeser || property != beanProperty.orNull || valueTypeDeser != typeDeser)
36-
new OptionDeserializer(elementType, typeDeser, Option(property), deser2.asInstanceOf[Option[JsonDeserializer[AnyRef]]])
37-
else this
31+
new OptionDeserializer(fullType, typeDeser, valueDeser.asInstanceOf[Option[JsonDeserializer[AnyRef]]], beanProperty)
3832
}
3933

40-
def hasContentTypeAnnotation(ctxt: DeserializationContext, property: BeanProperty) = (for {
41-
p <- Option(property)
42-
intr <- Option(ctxt.getAnnotationIntrospector)
43-
} yield {
44-
intr.refineDeserializationType(ctxt.getConfig, p.getMember, p.getType)
45-
}).isDefined
34+
override def createContextual(ctxt: DeserializationContext, property: BeanProperty): JsonDeserializer[Option[AnyRef]] = {
35+
val typeDeser = valueTypeDeserializer.map(_.forProperty(property))
36+
var deser = valueDeserializer
37+
var typ = fullType
38+
39+
def refdType() = Option(typ.getContentType).getOrElse(TypeFactory.unknownType())
4640

47-
override def deserialize(jp: JsonParser, ctxt: DeserializationContext) = valueTypeDeser match {
48-
case Some(d) => deserializeWithType(jp, ctxt, d)
49-
case None => Option {
50-
elementDeser.map(_.deserialize(jp, ctxt)).getOrElse {
51-
ctxt.findContextualValueDeserializer(elementType, beanProperty.orNull).deserialize(jp, ctxt)
41+
if (deser.isEmpty) {
42+
if (property != null) {
43+
val intr = ctxt.getAnnotationIntrospector
44+
val member = property.getMember
45+
if (intr != null && member != null) {
46+
typ = intr.refineDeserializationType(ctxt.getConfig, member, typ)
47+
}
48+
deser = Option(ctxt.findContextualValueDeserializer(refdType(), property))
5249
}
53-
}.asInstanceOf[Option[AnyRef]]
50+
} else { // otherwise directly assigned, probably not contextual yet:
51+
deser = Option(ctxt.handleSecondaryContextualization(deser.get, property, refdType()).asInstanceOf[JsonDeserializer[AnyRef]])
52+
}
53+
54+
withResolved(typ, typeDeser, deser, Option(property))
5455
}
5556

56-
override def deserializeWithType(jp: JsonParser, ctxt: DeserializationContext, typeDeserializer: TypeDeserializer) = Option {
57-
elementDeser.map(_.deserializeWithType(jp, ctxt, typeDeserializer)).getOrElse {
58-
ctxt.findContextualValueDeserializer(elementType, beanProperty.orNull).deserializeWithType(jp, ctxt, typeDeserializer)
57+
override def deserialize(p: JsonParser, ctxt: DeserializationContext): Option[AnyRef] = {
58+
val deser = valueDeserializer.getOrElse(ctxt.findContextualValueDeserializer(fullType.getContentType, beanProperty.orNull))
59+
val refd: AnyRef = valueTypeDeserializer match {
60+
case None => deser.deserialize(p, ctxt)
61+
case Some(vtd) => deser.deserializeWithType(p, ctxt, vtd)
5962
}
63+
Option(refd)
6064
}
6165

62-
override def getNullValue = None
66+
override def deserializeWithType(jp: JsonParser, ctxt: DeserializationContext, typeDeserializer: TypeDeserializer): Option[AnyRef] = {
67+
val t = jp.getCurrentToken
68+
if (t == JsonToken.VALUE_NULL) {
69+
getNullValue(ctxt)
70+
} else {
71+
typeDeserializer.deserializeTypedFromAny(jp, ctxt).asInstanceOf[Option[AnyRef]]
72+
}
73+
}
6374
}
6475

6576
private object OptionDeserializerResolver extends Deserializers.Base {
6677

6778
private val OPTION = classOf[Option[AnyRef]]
6879

69-
override def findCollectionLikeDeserializer(theType: CollectionLikeType,
70-
config: DeserializationConfig,
71-
beanDesc: BeanDescription,
72-
elementTypeDeserializer: TypeDeserializer,
73-
elementValueDeserializer: JsonDeserializer[_]) =
74-
if (!OPTION.isAssignableFrom(theType.getRawClass)) null
80+
override def findReferenceDeserializer(refType: ReferenceType,
81+
config: DeserializationConfig,
82+
beanDesc: BeanDescription,
83+
contentTypeDeserializer: TypeDeserializer,
84+
contentDeserializer: JsonDeserializer[_]): JsonDeserializer[_] = {
85+
if (!OPTION.isAssignableFrom(refType.getRawClass)) null
7586
else {
76-
val elementType = theType.getContentType
77-
val typeDeser = Option(elementTypeDeserializer).orElse(Option(elementType.getTypeHandler.asInstanceOf[TypeDeserializer]))
78-
val valDeser: Option[JsonDeserializer[_]] = Option(elementValueDeserializer).orElse(Option(elementType.getValueHandler))
79-
new OptionDeserializer(elementType, typeDeser, None, valDeser)
87+
val elementType = refType.getContentType
88+
val typeDeser = Option(contentTypeDeserializer).orElse(Option(elementType.getTypeHandler[TypeDeserializer]))
89+
val valDeser = Option(contentDeserializer).orElse(Option(elementType.getValueHandler)).asInstanceOf[Option[JsonDeserializer[AnyRef]]]
90+
new OptionDeserializer(refType, typeDeser, valDeser)
8091
}
92+
}
8193
}
8294

8395
trait OptionDeserializerModule extends OptionTypeModifierModule {

src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionDeserializerTest.scala

-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ class OptionDeserializerTest extends DeserializerTest {
5656
deserialize[Option[Long]]("1") should be (Some(1L))
5757
deserialize[Option[Long]]("1").map(java.lang.Long.valueOf(_)) should be (Some(1L))
5858
deserialize[Option[Long]]("1").get.getClass should be (classOf[Long])
59-
6059
}
6160

6261
it should "sythensize None for optional fields that are non-existent" in {

src/test/scala/com/fasterxml/jackson/module/scala/deser/PrimitiveContainerTest.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize
55
import org.junit.runner.RunWith
66
import org.scalatest.junit.JUnitRunner
77

8-
object PrimitiveContainerTest
9-
{
8+
object PrimitiveContainerTest {
9+
1010
case class OptionInt(value: Option[Int])
1111
case class AnnotatedOptionInt(@JsonDeserialize(contentAs = classOf[java.lang.Integer]) value: Option[Int])
1212
case class OptionLong(value: Option[Long])

0 commit comments

Comments
 (0)