Skip to content

Commit

Permalink
Fix #100
Browse files Browse the repository at this point in the history
  • Loading branch information
christophercurrie committed Oct 18, 2013
1 parent 918ffaf commit 5cd381d
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 20 deletions.
2 changes: 2 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ scalacOptions <+= (scalaBinaryVersion) map { binVer => binVer match {
case _ => "-target:jvm-1.6"
} }

libraryDependencies <++= (scalaVersion) { (ver) => Seq("org.scala-lang" % "scalap" % ver) }

libraryDependencies <++= (version) { (v) => Seq(
"com.fasterxml.jackson.core" % "jackson-core" % v,
"com.fasterxml.jackson.core" % "jackson-annotations" % v,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.fasterxml.jackson.module.scala

import com.fasterxml.jackson.module.scala.deser.UnsortedSetDeserializerModule
import com.fasterxml.jackson.module.scala.deser.{SortedSetDeserializerModule, UnsortedSetDeserializerModule}

trait SetModule extends UnsortedSetDeserializerModule {
trait SetModule extends UnsortedSetDeserializerModule with SortedSetDeserializerModule {

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.fasterxml.jackson.databind.jsontype.{TypeDeserializer}
import com.fasterxml.jackson.databind.`type`.MapLikeType
import com.fasterxml.jackson.module.scala.modifiers.MapTypeModifierModule
import deser.{ContextualDeserializer, Deserializers, ValueInstantiator}
import com.fasterxml.jackson.module.scala.introspect.OrderingLocator

private class SortedMapBuilderWrapper[K,V](val builder: mutable.Builder[(K,V), SortedMap[K,V]]) extends AbstractMap[K,V] {
override def put(k: K, v: V) = { builder += ((k,v)); v }
Expand All @@ -22,11 +23,9 @@ private class SortedMapBuilderWrapper[K,V](val builder: mutable.Builder[(K,V), S
}

private object SortedMapDeserializer {
def orderingFor(cls: Class[_]): Ordering[AnyRef] =
(if (classOf[String].isAssignableFrom(cls)) Ordering.String else
throw new IllegalArgumentException("Unsupported key type: " + cls.getCanonicalName)).asInstanceOf[Ordering[AnyRef]]
def orderingFor = OrderingLocator.locate _

def builderFor(cls: Class[_], keyCls: Class[_]): mutable.Builder[(AnyRef,AnyRef), SortedMap[AnyRef,AnyRef]] =
def builderFor(cls: Class[_], keyCls: JavaType): mutable.Builder[(AnyRef,AnyRef), SortedMap[AnyRef,AnyRef]] =
if (classOf[TreeMap[_,_]].isAssignableFrom(cls)) TreeMap.newBuilder[AnyRef,AnyRef](orderingFor(keyCls)) else
SortedMap.newBuilder[AnyRef,AnyRef](orderingFor(keyCls))
}
Expand All @@ -37,17 +36,18 @@ private class SortedMapDeserializer(
keyDeser: KeyDeserializer,
valueDeser: JsonDeserializer[_],
valueTypeDeser: TypeDeserializer)
extends ContainerDeserializerBase[SortedMap[_,_]](classOf[SortedMapDeserializer])
extends ContainerDeserializerBase[SortedMap[_,_]](collectionType)
with ContextualDeserializer {

private val javaContainerType = config.constructType(classOf[MapBuilderWrapper[AnyRef,AnyRef]])
private val javaContainerType =
config.getTypeFactory.constructMapLikeType(classOf[MapBuilderWrapper[_,_]], collectionType.containedType(0), collectionType.containedType(1))

private val instantiator =
new ValueInstantiator {
def getValueTypeDesc = collectionType.getRawClass.getCanonicalName
override def canCreateUsingDefault = true
override def createUsingDefault(ctx: DeserializationContext) =
new SortedMapBuilderWrapper[AnyRef,AnyRef](SortedMapDeserializer.builderFor(collectionType.getRawClass, collectionType.containedType(0).getRawClass))
new SortedMapBuilderWrapper[AnyRef,AnyRef](SortedMapDeserializer.builderFor(collectionType.getRawClass, collectionType.containedType(0)))
}

private val containerDeserializer =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.fasterxml.jackson.module.scala.deser

import com.fasterxml.jackson.module.scala.modifiers.SetTypeModifierModule
import com.fasterxml.jackson.databind.deser.{ValueInstantiator, ContextualDeserializer, Deserializers}
import com.fasterxml.jackson.databind.deser.std.{StdValueInstantiator, CollectionDeserializer, ContainerDeserializerBase}
import com.fasterxml.jackson.databind._
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer
import com.fasterxml.jackson.core.JsonParser
import scala.collection.{immutable, SortedSet, mutable}
import com.fasterxml.jackson.databind.`type`.CollectionLikeType
import java.util.AbstractCollection
import com.fasterxml.jackson.module.scala.introspect.OrderingLocator
import java.lang.Object

private class SortedSetBuilderWrapper[E](val builder: mutable.Builder[E, _ <: collection.SortedSet[E]]) extends AbstractCollection[E] {

override def add(e: E) = { builder += e; true }

// Required by AbstractCollection, but the deserializer doesn't care about them.
def iterator() = null
def size() = 0
}

private object SortedSetDeserializer {
def orderingFor = OrderingLocator.locate _

def builderFor(cls: Class[_], valueType: JavaType): mutable.Builder[AnyRef, SortedSet[AnyRef]] =
if (classOf[mutable.TreeSet[_]].isAssignableFrom(cls)) mutable.TreeSet.newBuilder[AnyRef](orderingFor(valueType)) else
if (classOf[mutable.SortedSet[_]].isAssignableFrom(cls)) mutable.SortedSet.newBuilder[AnyRef](orderingFor(valueType)) else
if (classOf[immutable.TreeSet[_]].isAssignableFrom(cls)) immutable.TreeSet.newBuilder[AnyRef](orderingFor(valueType)) else
immutable.SortedSet.newBuilder[AnyRef](orderingFor(valueType))
}

private class SortedSetInstantiator(config: DeserializationConfig, collectionType: Class[_], valueType: JavaType)
extends StdValueInstantiator(config, collectionType) {

override def canCreateUsingDefault = true

override def createUsingDefault(ctxt: DeserializationContext) =
new SortedSetBuilderWrapper[AnyRef](SortedSetDeserializer.builderFor(collectionType, valueType))
}


private class SortedSetDeserializer(collectionType: JavaType, containerDeserializer: CollectionDeserializer)
extends ContainerDeserializerBase[collection.SortedSet[_]](collectionType)
with ContextualDeserializer
{
def this(collectionType: JavaType, valueDeser: JsonDeserializer[Object], valueTypeDeser: TypeDeserializer, valueInstantiator: ValueInstantiator) =
this(collectionType, new CollectionDeserializer(collectionType, valueDeser, valueTypeDeser, valueInstantiator))

def createContextual(ctxt: DeserializationContext, property: BeanProperty) = {
val newDelegate = containerDeserializer.createContextual(ctxt, property)
new SortedSetDeserializer(collectionType, newDelegate)
}

override def getContentType = containerDeserializer.getContentType

override def getContentDeserializer = containerDeserializer.getContentDeserializer

override def deserialize(jp: JsonParser, ctxt: DeserializationContext): collection.SortedSet[_] =
containerDeserializer.deserialize(jp, ctxt) match {
case wrapper: SortedSetBuilderWrapper[_] => wrapper.builder.result()
}
}

private object SortedSetDeserializerResolver extends Deserializers.Base {
private final val SORTED_SET = classOf[collection.SortedSet[_]]

override def findCollectionLikeDeserializer(collectionType: CollectionLikeType,
config: DeserializationConfig,
beanDesc: BeanDescription,
elementTypeDeserializer: TypeDeserializer,
elementDeserializer: JsonDeserializer[_]): JsonDeserializer[_] = {
val rawClass = collectionType.getRawClass

if (!SORTED_SET.isAssignableFrom(rawClass)) null
else {
val deser = elementDeserializer.asInstanceOf[JsonDeserializer[AnyRef]]
val instantiator = new SortedSetInstantiator(config, rawClass, collectionType.containedType(0))
new SortedSetDeserializer(collectionType, deser, elementTypeDeserializer, instantiator)
}
}

}

trait SortedSetDeserializerModule extends SetTypeModifierModule {
this += (_ addDeserializers SortedSetDeserializerResolver)
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ private object UnsortedSetDeserializer {
.toList

def companionFor(cls: Class[_]): GenericCompanion[collection.Set] =
COMPANIONS find { _._1.isAssignableFrom(cls) } map { _._2 } getOrElse (Set)
COMPANIONS find { _._1.isAssignableFrom(cls) } map { _._2 } getOrElse Set

def builderFor[A](cls: Class[_]): mutable.Builder[A, collection.Set[A]] = companionFor(cls).newBuilder[A]
}
Expand All @@ -48,14 +48,14 @@ private class SetInstantiator(config: DeserializationConfig, valueType: Class[_]
}

private class UnsortedSetDeserializer(collectionType: JavaType, containerDeserializer: CollectionDeserializer)
extends ContainerDeserializerBase[collection.Set[_]](collectionType.getRawClass)
extends ContainerDeserializerBase[collection.Set[_]](collectionType)
with ContextualDeserializer {

def this(collectionType: JavaType, valueDeser: JsonDeserializer[Object], valueTypeDeser: TypeDeserializer, valueInstantiator: ValueInstantiator) =
this(collectionType, new CollectionDeserializer(collectionType, valueDeser, valueTypeDeser, valueInstantiator))

def createContextual(ctxt: DeserializationContext, property: BeanProperty) = {
val newDelegate = containerDeserializer.createContextual(ctxt, property).asInstanceOf[CollectionDeserializer]
val newDelegate = containerDeserializer.createContextual(ctxt, property)
new UnsortedSetDeserializer(collectionType, newDelegate)
}

Expand All @@ -71,8 +71,9 @@ private class UnsortedSetDeserializer(collectionType: JavaType, containerDeseria

private object UnsortedSetDeserializerResolver extends Deserializers.Base {

lazy final val SET = classOf[collection.Set[_]]

private final val SET = classOf[collection.Set[_]]
private final val SORTED_SET = classOf[collection.SortedSet[_]]

override def findCollectionLikeDeserializer(collectionType: CollectionLikeType,
config: DeserializationConfig,
beanDesc: BeanDescription,
Expand All @@ -81,6 +82,7 @@ private object UnsortedSetDeserializerResolver extends Deserializers.Base {
val rawClass = collectionType.getRawClass

if (!SET.isAssignableFrom(rawClass)) null
else if (SORTED_SET.isAssignableFrom(rawClass)) null
else {
val deser = elementDeserializer.asInstanceOf[JsonDeserializer[AnyRef]]
val instantiator = new SetInstantiator(config, rawClass)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.fasterxml.jackson.module.scala.introspect

import com.fasterxml.jackson.databind.JavaType

object OrderingLocator {
val ORDERINGS = Map(
classOf[Unit] -> Ordering.Unit,
classOf[Boolean] -> Ordering.Boolean,
classOf[Byte] -> Ordering.Byte,
classOf[Char] -> Ordering.Char,
classOf[Short] -> Ordering.Short,
classOf[Int] -> Ordering.Int,
classOf[Long] -> Ordering.Long,
classOf[Float] -> Ordering.Float,
classOf[Double] -> Ordering.Double,
classOf[BigInt] -> Ordering.BigInt,
classOf[BigDecimal] -> Ordering.BigDecimal,
classOf[String] -> Ordering.String
)

def locate(javaType: JavaType): Ordering[AnyRef] = {
def matches(other: Class[_]) = other.isAssignableFrom(javaType.getRawClass)
val ordering =
ORDERINGS.find(e => matches(e._1)).map(_._2).getOrElse {
if (matches(classOf[Option[_]])) {
val delegate = locate(javaType.containedType(0))
Ordering.Option(delegate)
}
else if (matches(classOf[Comparable[_]]))
new Ordering[AnyRef] {
def compare(x: AnyRef, y: AnyRef): Int = {
x.asInstanceOf[Comparable[AnyRef]].compareTo(y)
}
}

else throw new IllegalArgumentException("Unsupported value type: " + javaType.getRawClass.getCanonicalName)
}

ordering.asInstanceOf[Ordering[AnyRef]]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ package com.fasterxml.jackson.module.scala.modifiers

import java.lang.reflect.Type

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.`type`.{TypeBindings, TypeFactory, TypeModifier};
import com.fasterxml.jackson.databind.JavaType
import com.fasterxml.jackson.databind.`type`.{TypeBindings, TypeFactory, TypeModifier}

private [modifiers] trait CollectionLikeTypeModifier extends TypeModifier with GenTypeModifier {

def BASE: Class[_]

override def modifyType(originalType: JavaType, jdkType: Type, context: TypeBindings, typeFactory: TypeFactory) =
if (originalType.containedTypeCount() > 1) originalType else
classObjectFor(jdkType) find (BASE.isAssignableFrom(_)) map { cls =>
classObjectFor(jdkType) find BASE.isAssignableFrom map { cls =>
val eltType = if (originalType.containedTypeCount() == 1) originalType.containedType(0) else UNKNOWN
typeFactory.constructCollectionLikeType(cls, eltType)
} getOrElse originalType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ class SortedMapDeserializerTest extends DeserializerTest with FlatSpec with Shou
result should equal (variantMapScala)
}

ignore should "deserialize an object with numeric keys into a SortedMap" in {
val result = deserialize[SortedMap[Int,String]](numericMapJson)
it should "deserialize an object with numeric keys into a SortedMap" in {
// NB: This is `java.lang.Integer` because of GH-104
val result = deserialize[SortedMap[Integer,String]](numericMapJson)
result should equal (numericMapScala)
}

Expand All @@ -40,5 +41,5 @@ class SortedMapDeserializerTest extends DeserializerTest with FlatSpec with Shou
val variantMapJson = """{ "one": "1", "two": 2 }"""
val variantMapScala = SortedMap[String,Any]("one"->"1","two"->2)
val numericMapJson = """{ "1": "one", "2": "two" }"""
val numericMapScala = SortedMap(1->"one",2->"two")
val numericMapScala = SortedMap[Integer,String](Integer.valueOf(1)->"one",Integer.valueOf(2)->"two")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.fasterxml.jackson.module.scala.deser

import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.matchers.ShouldMatchers
import scala.collection.mutable
import scala.collection.SortedSet
import scala.collection.immutable.TreeSet
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.core.`type`.TypeReference

object SortedSetDeserializerTest
{
case class LongSetHolder(@JsonDeserialize(contentAs = classOf[java.lang.Long]) value: SortedSet[Long])

case class ComparableBean(value: Long) extends Comparable[ComparableBean] {
def compareTo(o: ComparableBean): Int = value compareTo o.value
}
}

@RunWith(classOf[JUnitRunner])
class SortedSetDeserializerTest extends DeserializationFixture with ShouldMatchers {
import SortedSetDeserializerTest._

behavior of "SortedSetDeserializer"

it should "deserialize a list into a SortedSet" in { f =>
val result = f.readValue[SortedSet[String]](setJson)
result should be === setScala
}

it should "deserialize a list into a mutable SortedSet" in { f =>
val result = f.readValue[mutable.SortedSet[String]](setJson)
result should be === setScala
}

it should "deserialize a list into a TreeSet" in { f =>
val result = f.readValue[TreeSet[String]](setJson)
result should be === setScala
}

it should "deserialize a list into a mutable TreeSet" in { f =>
val result = f.readValue[mutable.TreeSet[String]](setJson)
result should be === setScala
}

it should "deserialize a list of Ints into a SortedSet" in { f =>
val result = f.readValue[SortedSet[Int]](intSetJson)
result should be === intSetScala
}

it should "deserialize a list of Longs into a SortedSet" in { f =>
val result = f.readValue[LongSetHolder](longSetHolderJson)
result should be === longSetHolderScala
}

it should "deserialize a list of Comparables into a SortedSet" in { f =>
val result = f.readValue[SortedSet[ComparableBean]](comparableSetJson)
result should be === comparableSetScala
}

it should "deserialize a lit of Ints into a SortedSet of Options" in { f =>
// NB: This is `java.lang.Integer`, because of GH-104
val result = f.readValue[SortedSet[Option[Integer]]](intSetJson, new TypeReference[SortedSet[Option[Integer]]]{})
result should be === optionIntSetScala
}

val setJson = """[ "one", "two", "three" ]"""
val setScala = SortedSet("one", "two", "three")
val intSetJson = """[ 1, 2, 3 ]"""
val intSetScala = SortedSet(3, 2, 1)
val longSetHolderJson = """{"value":[1,2,3]}"""
val longSetHolderScala = LongSetHolder(SortedSet(3,2,1))
val comparableSetJson = """[{"value": 3},{"value": 1},{"value": 2}]"""
val comparableSetScala = SortedSet(ComparableBean(2), ComparableBean(3), ComparableBean(1))
val optionIntSetScala = SortedSet(Option(2), Option(3), Option(1))

}

0 comments on commit 5cd381d

Please sign in to comment.