Skip to content
Closed
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