Skip to content

Upgrade Option support to use ReferenceType. #252

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
20 changes: 12 additions & 8 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ organization := "com.fasterxml.jackson.module"

scalaVersion := "2.11.8"

crossScalaVersions := Seq("2.10.6", "2.11.8", "2.12.0-M4")
crossScalaVersions := Seq("2.10.6", "2.11.8", "2.12.0-M4)

scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature")

Expand Down Expand Up @@ -35,16 +35,20 @@ scalacOptions ++= (
}
)

val jacksonVersion = "2.7.3"

val jacksonSnapshot = "2.7.4-SNAPSHOT"

libraryDependencies ++= Seq(
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
"com.fasterxml.jackson.core" % "jackson-core" % "2.7.2",
"com.fasterxml.jackson.core" % "jackson-annotations" % "2.7.2",
"com.fasterxml.jackson.core" % "jackson-databind" % "2.7.2",
"com.fasterxml.jackson.module" % "jackson-module-paranamer" % "2.7.2",
"com.fasterxml.jackson.core" % "jackson-core" % jacksonVersion,
"com.fasterxml.jackson.core" % "jackson-annotations" % jacksonVersion,
"com.fasterxml.jackson.core" % "jackson-databind" % jacksonSnapshot,
"com.fasterxml.jackson.module" % "jackson-module-paranamer" % jacksonVersion,
// test dependencies
"com.fasterxml.jackson.datatype" % "jackson-datatype-joda" % "2.7.2" % "test",
"com.fasterxml.jackson.datatype" % "jackson-datatype-guava" % "2.7.2" % "test",
"com.fasterxml.jackson.module" % "jackson-module-jsonSchema" % "2.7.2" % "test",
"com.fasterxml.jackson.datatype" % "jackson-datatype-joda" % jacksonVersion % "test",
"com.fasterxml.jackson.datatype" % "jackson-datatype-guava" % jacksonVersion % "test",
"com.fasterxml.jackson.module" % "jackson-module-jsonSchema" % jacksonVersion % "test",
"org.scalatest" %% "scalatest" % "2.2.6" % "test",
"junit" % "junit" % "4.11" % "test"
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,83 +1,95 @@
package com.fasterxml.jackson.module.scala.deser

import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.{JsonToken, JsonParser}
import com.fasterxml.jackson.databind._
import com.fasterxml.jackson.databind.`type`.CollectionLikeType
import com.fasterxml.jackson.databind.`type`.{ReferenceType, TypeFactory, CollectionLikeType}
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.deser.{ContextualDeserializer, Deserializers}
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer
import com.fasterxml.jackson.module.scala.modifiers.OptionTypeModifierModule

private class OptionDeserializer(elementType: JavaType,
valueTypeDeser: Option[TypeDeserializer],
beanProperty: Option[BeanProperty],
elementDeser: Option[JsonDeserializer[_]])
extends StdDeserializer[Option[AnyRef]](classOf[Option[AnyRef]]) with ContextualDeserializer {

override def createContextual(ctxt: DeserializationContext, property: BeanProperty): JsonDeserializer[_] = {
val typeDeser = valueTypeDeser.map(_.forProperty(property))
val deser: Option[JsonDeserializer[_]] =
(for {
p <- Option(property)
m <- Option(p.getMember)
deserDef <- Option(ctxt.getAnnotationIntrospector.findContentDeserializer(m))
} yield ctxt.deserializerInstance(m, deserDef)).orElse(elementDeser)
val deser1: Option[JsonDeserializer[_]] = Option(findConvertingContentDeserializer(ctxt, property, deser.orNull))
val deser2: Option[JsonDeserializer[_]] = if (deser1.isEmpty) {
if (hasContentTypeAnnotation(ctxt, property)) {
Option(ctxt.findContextualValueDeserializer(elementType, property))
} else {
deser1
}
} else {
Option(ctxt.handleSecondaryContextualization(deser1.get, property, elementType))
private class OptionDeserializer(fullType: JavaType,
valueTypeDeserializer: Option[TypeDeserializer],
valueDeserializer: Option[JsonDeserializer[AnyRef]],
beanProperty: Option[BeanProperty] = None)
extends StdDeserializer[Option[AnyRef]](fullType) with ContextualDeserializer {

override def getValueType: JavaType = fullType

override def getNullValue: Option[AnyRef] = None

private[this] def withResolved(fullType: JavaType,
typeDeser: Option[TypeDeserializer],
valueDeser: Option[JsonDeserializer[_]],
beanProperty: Option[BeanProperty]): OptionDeserializer = {
if (fullType == this.fullType &&
typeDeser == this.valueTypeDeserializer &&
valueDeser == this.valueDeserializer &&
beanProperty == this.beanProperty) {
return this
}
if (deser2 != elementDeser || property != beanProperty.orNull || valueTypeDeser != typeDeser)
new OptionDeserializer(elementType, typeDeser, Option(property), deser2.asInstanceOf[Option[JsonDeserializer[AnyRef]]])
else this
new OptionDeserializer(fullType, typeDeser, valueDeser.asInstanceOf[Option[JsonDeserializer[AnyRef]]], beanProperty)
}

def hasContentTypeAnnotation(ctxt: DeserializationContext, property: BeanProperty) = (for {
p <- Option(property)
intr <- Option(ctxt.getAnnotationIntrospector)
} yield {
intr.refineDeserializationType(ctxt.getConfig, p.getMember, p.getType)
}).isDefined
override def createContextual(ctxt: DeserializationContext, property: BeanProperty): JsonDeserializer[Option[AnyRef]] = {
val typeDeser = valueTypeDeserializer.map(_.forProperty(property))
var deser = valueDeserializer
var typ = fullType

def refdType() = Option(typ.getContentType).getOrElse(TypeFactory.unknownType())

override def deserialize(jp: JsonParser, ctxt: DeserializationContext) = valueTypeDeser match {
case Some(d) => deserializeWithType(jp, ctxt, d)
case None => Option {
elementDeser.map(_.deserialize(jp, ctxt)).getOrElse {
ctxt.findContextualValueDeserializer(elementType, beanProperty.orNull).deserialize(jp, ctxt)
if (deser.isEmpty) {
if (property != null) {
val intr = ctxt.getAnnotationIntrospector
val member = property.getMember
if (intr != null && member != null) {
typ = intr.refineDeserializationType(ctxt.getConfig, member, typ)
}
deser = Option(ctxt.findContextualValueDeserializer(refdType(), property))
}
}.asInstanceOf[Option[AnyRef]]
} else { // otherwise directly assigned, probably not contextual yet:
deser = Option(ctxt.handleSecondaryContextualization(deser.get, property, refdType()).asInstanceOf[JsonDeserializer[AnyRef]])
}

withResolved(typ, typeDeser, deser, Option(property))
}

override def deserializeWithType(jp: JsonParser, ctxt: DeserializationContext, typeDeserializer: TypeDeserializer) = Option {
elementDeser.map(_.deserializeWithType(jp, ctxt, typeDeserializer)).getOrElse {
ctxt.findContextualValueDeserializer(elementType, beanProperty.orNull).deserializeWithType(jp, ctxt, typeDeserializer)
override def deserialize(p: JsonParser, ctxt: DeserializationContext): Option[AnyRef] = {
val deser = valueDeserializer.getOrElse(ctxt.findContextualValueDeserializer(fullType.getContentType, beanProperty.orNull))
val refd: AnyRef = valueTypeDeserializer match {
case None => deser.deserialize(p, ctxt)
case Some(vtd) => deser.deserializeWithType(p, ctxt, vtd)
}
Option(refd)
}

override def getNullValue = None
override def deserializeWithType(jp: JsonParser, ctxt: DeserializationContext, typeDeserializer: TypeDeserializer): Option[AnyRef] = {
val t = jp.getCurrentToken
if (t == JsonToken.VALUE_NULL) {
getNullValue(ctxt)
} else {
typeDeserializer.deserializeTypedFromAny(jp, ctxt).asInstanceOf[Option[AnyRef]]
}
}
}

private object OptionDeserializerResolver extends Deserializers.Base {

private val OPTION = classOf[Option[AnyRef]]

override def findCollectionLikeDeserializer(theType: CollectionLikeType,
config: DeserializationConfig,
beanDesc: BeanDescription,
elementTypeDeserializer: TypeDeserializer,
elementValueDeserializer: JsonDeserializer[_]) =
if (!OPTION.isAssignableFrom(theType.getRawClass)) null
override def findReferenceDeserializer(refType: ReferenceType,
config: DeserializationConfig,
beanDesc: BeanDescription,
contentTypeDeserializer: TypeDeserializer,
contentDeserializer: JsonDeserializer[_]): JsonDeserializer[_] = {
if (!OPTION.isAssignableFrom(refType.getRawClass)) null
else {
val elementType = theType.getContentType
val typeDeser = Option(elementTypeDeserializer).orElse(Option(elementType.getTypeHandler.asInstanceOf[TypeDeserializer]))
val valDeser: Option[JsonDeserializer[_]] = Option(elementValueDeserializer).orElse(Option(elementType.getValueHandler))
new OptionDeserializer(elementType, typeDeser, None, valDeser)
val elementType = refType.getContentType
val typeDeser = Option(contentTypeDeserializer).orElse(Option(elementType.getTypeHandler[TypeDeserializer]))
val valDeser = Option(contentDeserializer).orElse(Option(elementType.getValueHandler)).asInstanceOf[Option[JsonDeserializer[AnyRef]]]
new OptionDeserializer(refType, typeDeser, valDeser)
}
}
}

trait OptionDeserializerModule extends OptionTypeModifierModule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ trait ScalaObjectMapper {
throw new IllegalArgumentException("Need exactly 1 type parameter for collection like types ("+clazz.getName+")")
}
getTypeFactory.constructCollectionLikeType(clazz, typeArguments(0))
} else if (isReference(clazz)) {
val typeArguments = m.typeArguments.map(constructType(_)).toArray
if(typeArguments.length != 1) {
throw new IllegalArgumentException("Need exactly 1 type parameter for reference types ("+clazz.getName+")")
}
getTypeFactory.constructReferenceType(clazz, typeArguments(0))
} else {
val typeArguments = m.typeArguments.map(constructType(_)).toArray
getTypeFactory.constructParametrizedType(clazz, clazz, typeArguments: _*)
Expand Down Expand Up @@ -333,10 +339,14 @@ trait ScalaObjectMapper {
MAP.isAssignableFrom(c)
}

private val ITERABLE = classOf[collection.Iterable[_]]
private val OPTION = classOf[Option[_]]
private def isReference(c: Class[_]): Boolean = {
OPTION.isAssignableFrom(c)
}

private val ITERABLE = classOf[collection.Iterable[_]]
private def isCollectionLike(c: Class[_]): Boolean = {
ITERABLE.isAssignableFrom(c) || OPTION.isAssignableFrom(c)
ITERABLE.isAssignableFrom(c)
}

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
package com.fasterxml.jackson.module.scala.modifiers

import java.lang.reflect.Type

import com.fasterxml.jackson.databind.JavaType
import com.fasterxml.jackson.databind.`type`.{ReferenceType, TypeBindings, TypeFactory, TypeModifier}
import com.fasterxml.jackson.module.scala.JacksonModule

private object OptionTypeModifier extends CollectionLikeTypeModifier {
def BASE = classOf[Option[Any]]
private object OptionTypeModifier extends TypeModifier with GenTypeModifier {
def OPTION = classOf[Option[AnyRef]]

override def modifyType(typ: JavaType, jdkType: Type, context: TypeBindings, typeFactory: TypeFactory): JavaType = {
if (typ.isReferenceType || typ.isContainerType) return typ

if (classObjectFor(jdkType).exists(OPTION.isAssignableFrom)) {
ReferenceType.upgradeFrom(typ, typ.containedTypeOrUnknown(0))
} else typ
}
}

trait OptionTypeModifierModule extends JacksonModule {
this += OptionTypeModifier
}
}
Loading