From b23c84a8a0ecd76b98258390a3a8ad9523998826 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 20 Aug 2022 13:12:49 +0100 Subject: [PATCH 01/12] Create ImmutableIntMapDeserializer.scala --- .../deser/ImmutableIntMapDeserializer.scala | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala b/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala new file mode 100644 index 000000000..7b815de3d --- /dev/null +++ b/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala @@ -0,0 +1,29 @@ +package com.fasterxml.jackson.module.scala.deser + +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.deser.std.StdDeserializer +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.module.scala.{DefaultScalaModule, IteratorModule} + +import scala.collection.immutable +import scala.languageFeature.postfixOps + +/** + * Adds support for deserializing Scala [[scala.collection.immutable.IntMap]]s. Scala IntMaps can already be + * serialized using [[IteratorModule]] or [[DefaultScalaModule]]. + * + * @since 2.14.0 + */ +object ImmutableIntMapDeserializer extends StdDeserializer[immutable.IntMap[_]](classOf[immutable.IntMap[_]]) { + override def deserialize(p: JsonParser, ctxt: DeserializationContext): immutable.IntMap[_] = { + val node: JsonNode = p.getCodec.readTree(p) + val iter = node.fields() + var map = immutable.IntMap[String]() + while (iter.hasNext) { + val entry = iter.next() + map += (entry.getKey.toInt, entry.getValue.asText()) + } + map + } +} From 9b253b39b62129c55d1973d7dbfe769189f65f90 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 20 Aug 2022 15:36:20 +0100 Subject: [PATCH 02/12] wip --- .../deser/ImmutableIntMapDeserializer.scala | 99 ++++++++++++++++--- .../deser/ImmutableIntMapDeserializer2.scala | 50 ++++++++++ 2 files changed, 135 insertions(+), 14 deletions(-) create mode 100644 src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer2.scala diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala b/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala index 7b815de3d..a6560193f 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala @@ -1,12 +1,15 @@ package com.fasterxml.jackson.module.scala.deser import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.deser.std.StdDeserializer -import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.`type`.MapLikeType +import com.fasterxml.jackson.databind.deser.{ContextualDeserializer, Deserializers, ValueInstantiator} +import com.fasterxml.jackson.databind.deser.std.{ContainerDeserializerBase, MapDeserializer, StdValueInstantiator} +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer +import com.fasterxml.jackson.databind._ import com.fasterxml.jackson.module.scala.{DefaultScalaModule, IteratorModule} -import scala.collection.immutable +import scala.collection.immutable.IntMap +import scala.collection.{Map, mutable} import scala.languageFeature.postfixOps /** @@ -15,15 +18,83 @@ import scala.languageFeature.postfixOps * * @since 2.14.0 */ -object ImmutableIntMapDeserializer extends StdDeserializer[immutable.IntMap[_]](classOf[immutable.IntMap[_]]) { - override def deserialize(p: JsonParser, ctxt: DeserializationContext): immutable.IntMap[_] = { - val node: JsonNode = p.getCodec.readTree(p) - val iter = node.fields() - var map = immutable.IntMap[String]() - while (iter.hasNext) { - val entry = iter.next() - map += (entry.getKey.toInt, entry.getValue.asText()) - } - map +class ImmutableIntMapDeserializerResolver extends Deserializers.Base { + override def findMapLikeDeserializer(theType: MapLikeType, + config: DeserializationConfig, + beanDesc: BeanDescription, + keyDeserializer: KeyDeserializer, + elementTypeDeserializer: TypeDeserializer, + elementDeserializer: JsonDeserializer[_]): JsonDeserializer[_] = { + if (!classOf[IntMap[_]].isAssignableFrom(theType.getRawClass)) None.orNull + else { + new IntMapDeserializer(theType, new Instantiator(config, theType), keyDeserializer, elementDeserializer, elementTypeDeserializer) + } + } + + private class Instantiator(config: DeserializationConfig, mapType: MapLikeType) extends StdValueInstantiator(config, mapType) { + override def canCreateUsingDefault = true + + override def createUsingDefault(ctxt: DeserializationContext) = + new BuilderWrapper[AnyRef](IntMap.newBuilder[AnyRef]) + } + + private class BuilderWrapper[V >: AnyRef](val builder: mutable.Builder[Int, V]) extends java.util.AbstractMap[Int, V] { + private var baseMap: Map[Any, V] = Map.empty + + override def put(k: Int, v: V): V = { + builder += ((k, v)); + v + } + + // Used by the deserializer when using readerForUpdating + override def get(key: Any): V = baseMap.get(key).orNull + + // Isn't used by the deserializer + override def entrySet(): java.util.Set[java.util.Map.Entry[Int, V]] = throw new UnsupportedOperationException + + def setInitialValue(init: Map[Int, V]): Unit = { + init.foreach(Function.tupled(put)) + baseMap = init.asInstanceOf[Map[Any, V]] + } + } + + private class IntMapDeserializer[V](mapType: MapLikeType, containerDeserializer: MapDeserializer) + extends ContainerDeserializerBase[IntMap[V]](mapType) with ContextualDeserializer { + + def this(mapType: MapLikeType, valueInstantiator: ValueInstantiator, keyDeser: KeyDeserializer, valueDeser: JsonDeserializer[_], valueTypeDeser: TypeDeserializer) = { + this(mapType, new MapDeserializer(mapType, valueInstantiator, keyDeser, valueDeser.asInstanceOf[JsonDeserializer[AnyRef]], valueTypeDeser)) + } + + override def getContentType: JavaType = containerDeserializer.getContentType + + override def getContentDeserializer: JsonDeserializer[AnyRef] = containerDeserializer.getContentDeserializer + + override def createContextual(ctxt: DeserializationContext, property: BeanProperty): JsonDeserializer[_] = { + val newDelegate = containerDeserializer.createContextual(ctxt, property).asInstanceOf[MapDeserializer] + new IntMapDeserializer(mapType, newDelegate) + } + + override def deserialize(jp: JsonParser, ctxt: DeserializationContext): IntMap[V] = { + containerDeserializer.deserialize(jp, ctxt) match { + case wrapper: BuilderWrapper[_] => wrapper.builder.result().asInstanceOf[IntMap[V]] + } + } + + override def deserialize(jp: JsonParser, ctxt: DeserializationContext, intoValue: IntMap[V]): IntMap[V] = { + val bw = newBuilderWrapper(ctxt) + bw.setInitialValue(intoValue.asInstanceOf[Map[Int, AnyRef]]) + containerDeserializer.deserialize(jp, ctxt, bw) match { + case wrapper: BuilderWrapper[_] => wrapper.builder.result().asInstanceOf[IntMap[V]] + } + } + + override def getEmptyValue(ctxt: DeserializationContext): Object = { + val bw = newBuilderWrapper(ctxt) + bw.builder.result().asInstanceOf[Object] + } + + private def newBuilderWrapper(ctxt: DeserializationContext): BuilderWrapper[AnyRef] = { + containerDeserializer.getValueInstantiator.createUsingDefault(ctxt).asInstanceOf[BuilderWrapper[AnyRef]] + } } } diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer2.scala b/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer2.scala new file mode 100644 index 000000000..4ecb66f49 --- /dev/null +++ b/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer2.scala @@ -0,0 +1,50 @@ +package com.fasterxml.jackson.module.scala.deser + +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.{BeanDescription, DeserializationConfig, DeserializationContext, JsonDeserializer, JsonNode, KeyDeserializer} +import com.fasterxml.jackson.databind.deser.std.{JsonNodeDeserializer, StdDeserializer} +import com.fasterxml.jackson.databind.`type`.MapLikeType +import com.fasterxml.jackson.databind.deser.Deserializers +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer +import com.fasterxml.jackson.databind.node.ObjectNode +import com.fasterxml.jackson.module.scala.{DefaultScalaModule, IteratorModule} + +import scala.collection.immutable +import scala.languageFeature.postfixOps + +/** + * Adds support for deserializing Scala [[scala.collection.immutable.IntMap]]s. Scala IntMaps can already be + * serialized using [[IteratorModule]] or [[DefaultScalaModule]]. + * + * @since 2.14.0 + */ +private class ImmutableIntMapDeserializer2(elementDeserializer: JsonDeserializer[_]) + extends StdDeserializer[immutable.IntMap[_]](classOf[immutable.IntMap[_]]) { + override def deserialize(p: JsonParser, ctxt: DeserializationContext): immutable.IntMap[_] = { + val objectNodeDeserializer = JsonNodeDeserializer.getDeserializer(classOf[ObjectNode]) + val node = objectNodeDeserializer.deserialize(p, ctxt).asInstanceOf[ObjectNode] + val iter = node.fields() + var map = immutable.IntMap[String]() + while (iter.hasNext) { + val entry = iter.next() + //entry.getValue + //elementDeserializer. + map += (entry.getKey.toInt, entry.getValue.asText()) + } + map + } +} + +class ImmutableIntMapDeserializerResolver2 extends Deserializers.Base { + override def findMapLikeDeserializer(theType: MapLikeType, + config: DeserializationConfig, + beanDesc: BeanDescription, + keyDeserializer: KeyDeserializer, + elementTypeDeserializer: TypeDeserializer, + elementDeserializer: JsonDeserializer[_]): JsonDeserializer[_] = { + if (!classOf[immutable.IntMap[_]].isAssignableFrom(theType.getRawClass)) None.orNull + else { + new ImmutableIntMapDeserializer2(elementDeserializer) + } + } +} From 2eb88de50fb135391d0891b5277d66bada3023b1 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 20 Aug 2022 16:34:38 +0100 Subject: [PATCH 03/12] wip --- .../deser/ImmutableIntMapDeserializer.scala | 104 +++++++----------- .../scala/deser/IntMapDeserializerTest.scala | 22 ++++ 2 files changed, 62 insertions(+), 64 deletions(-) create mode 100644 src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala b/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala index a6560193f..3ad27b9db 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala @@ -2,14 +2,17 @@ package com.fasterxml.jackson.module.scala.deser import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.databind.`type`.MapLikeType -import com.fasterxml.jackson.databind.deser.{ContextualDeserializer, Deserializers, ValueInstantiator} -import com.fasterxml.jackson.databind.deser.std.{ContainerDeserializerBase, MapDeserializer, StdValueInstantiator} +import com.fasterxml.jackson.databind.deser.Deserializers +import com.fasterxml.jackson.databind.deser.std.{JsonNodeDeserializer, MapDeserializer, StdDeserializer, StdValueInstantiator} import com.fasterxml.jackson.databind.jsontype.TypeDeserializer +import com.fasterxml.jackson.databind.node.ObjectNode import com.fasterxml.jackson.databind._ -import com.fasterxml.jackson.module.scala.{DefaultScalaModule, IteratorModule} +import com.fasterxml.jackson.module.scala.{DefaultScalaModule, IteratorModule, JacksonModule} +import java.util +import java.util.Map +import scala.collection.immutable import scala.collection.immutable.IntMap -import scala.collection.{Map, mutable} import scala.languageFeature.postfixOps /** @@ -18,83 +21,56 @@ import scala.languageFeature.postfixOps * * @since 2.14.0 */ -class ImmutableIntMapDeserializerResolver extends Deserializers.Base { +private object ImmutableIntMapDeserializerResolver extends Deserializers.Base { override def findMapLikeDeserializer(theType: MapLikeType, config: DeserializationConfig, beanDesc: BeanDescription, keyDeserializer: KeyDeserializer, elementTypeDeserializer: TypeDeserializer, elementDeserializer: JsonDeserializer[_]): JsonDeserializer[_] = { - if (!classOf[IntMap[_]].isAssignableFrom(theType.getRawClass)) None.orNull + if (!classOf[immutable.IntMap[_]].isAssignableFrom(theType.getRawClass)) None.orNull else { - new IntMapDeserializer(theType, new Instantiator(config, theType), keyDeserializer, elementDeserializer, elementTypeDeserializer) + new MapDeserializer(theType, new IntMapInstantiator(config, theType), keyDeserializer, + elementDeserializer.asInstanceOf[JsonDeserializer[AnyRef]], elementTypeDeserializer) } } - private class Instantiator(config: DeserializationConfig, mapType: MapLikeType) extends StdValueInstantiator(config, mapType) { - override def canCreateUsingDefault = true - - override def createUsingDefault(ctxt: DeserializationContext) = - new BuilderWrapper[AnyRef](IntMap.newBuilder[AnyRef]) - } - - private class BuilderWrapper[V >: AnyRef](val builder: mutable.Builder[Int, V]) extends java.util.AbstractMap[Int, V] { - private var baseMap: Map[Any, V] = Map.empty - - override def put(k: Int, v: V): V = { - builder += ((k, v)); - v - } - - // Used by the deserializer when using readerForUpdating - override def get(key: Any): V = baseMap.get(key).orNull - - // Isn't used by the deserializer - override def entrySet(): java.util.Set[java.util.Map.Entry[Int, V]] = throw new UnsupportedOperationException - - def setInitialValue(init: Map[Int, V]): Unit = { - init.foreach(Function.tupled(put)) - baseMap = init.asInstanceOf[Map[Any, V]] - } - } - - private class IntMapDeserializer[V](mapType: MapLikeType, containerDeserializer: MapDeserializer) - extends ContainerDeserializerBase[IntMap[V]](mapType) with ContextualDeserializer { + private class IntMapInstantiator(config: DeserializationConfig, mapType: MapLikeType) extends StdValueInstantiator(config, mapType) { + var intMap = IntMap[Any]() - def this(mapType: MapLikeType, valueInstantiator: ValueInstantiator, keyDeser: KeyDeserializer, valueDeser: JsonDeserializer[_], valueTypeDeser: TypeDeserializer) = { - this(mapType, new MapDeserializer(mapType, valueInstantiator, keyDeser, valueDeser.asInstanceOf[JsonDeserializer[AnyRef]], valueTypeDeser)) - } - - override def getContentType: JavaType = containerDeserializer.getContentType - - override def getContentDeserializer: JsonDeserializer[AnyRef] = containerDeserializer.getContentDeserializer - - override def createContextual(ctxt: DeserializationContext, property: BeanProperty): JsonDeserializer[_] = { - val newDelegate = containerDeserializer.createContextual(ctxt, property).asInstanceOf[MapDeserializer] - new IntMapDeserializer(mapType, newDelegate) - } + override def canCreateUsingDefault = true - override def deserialize(jp: JsonParser, ctxt: DeserializationContext): IntMap[V] = { - containerDeserializer.deserialize(jp, ctxt) match { - case wrapper: BuilderWrapper[_] => wrapper.builder.result().asInstanceOf[IntMap[V]] + override def createUsingDefault(ctxt: DeserializationContext) = new util.AbstractMap[Any, Any] { + override def put(k: Any, v: Any): Any = { + k match { + case i: Int => intMap += (i -> v) + case n: Number => intMap += (n.intValue() -> v) + case s: String => intMap += (s.toInt -> v) + case _ => { + val typeName = Option(k) match { + case Some(n) => n.getClass.getName + case _ => "null" + } + throw new IllegalArgumentException(s"IntMap does npt support keys of type $typeName") + } + } + v } - } - override def deserialize(jp: JsonParser, ctxt: DeserializationContext, intoValue: IntMap[V]): IntMap[V] = { - val bw = newBuilderWrapper(ctxt) - bw.setInitialValue(intoValue.asInstanceOf[Map[Int, AnyRef]]) - containerDeserializer.deserialize(jp, ctxt, bw) match { - case wrapper: BuilderWrapper[_] => wrapper.builder.result().asInstanceOf[IntMap[V]] + // Used by the deserializer when using readerForUpdating + override def get(key: Any): Any = key match { + case i: Int => intMap.get(i).orNull + case _ => None.orNull } - } - override def getEmptyValue(ctxt: DeserializationContext): Object = { - val bw = newBuilderWrapper(ctxt) - bw.builder.result().asInstanceOf[Object] + // Isn't used by the deserializer + override def entrySet(): java.util.Set[java.util.Map.Entry[Any, Any]] = throw new UnsupportedOperationException } - private def newBuilderWrapper(ctxt: DeserializationContext): BuilderWrapper[AnyRef] = { - containerDeserializer.getValueInstantiator.createUsingDefault(ctxt).asInstanceOf[BuilderWrapper[AnyRef]] - } + def asIntMap(): IntMap[_] = intMap } } + +trait IntMapDeserializerModule extends JacksonModule { + this += ImmutableIntMapDeserializerResolver +} diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala new file mode 100644 index 000000000..7ad60ca8f --- /dev/null +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala @@ -0,0 +1,22 @@ +package com.fasterxml.jackson.module.scala.deser + +import com.fasterxml.jackson.core.`type`.TypeReference +import com.fasterxml.jackson.module.scala.DefaultScalaModule + +import scala.collection.immutable.IntMap + +class IntMapDeserializerTest extends DeserializerTest { + + def module: DefaultScalaModule.type = DefaultScalaModule + + "Scala Module" should "deserialize IntMap" in { + val map = IntMap(1 -> 100L, 2 -> 200L) + + val mapper = newBuilder.addModule(new IntMapDeserializerModule() {}).build() + + val json = mapper.writeValueAsString(map) + val read = mapper.readValue(json, new TypeReference[IntMap[Long]]{}) + + read shouldBe map + } +} From 8de6f78a63edd0de3d58dbc89f4a33bd89c44e73 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 20 Aug 2022 16:53:53 +0100 Subject: [PATCH 04/12] wip --- .../deser/ImmutableIntMapDeserializer.scala | 88 +++++++++++++------ .../deser/ImmutableIntMapDeserializer2.scala | 50 ----------- .../scala/deser/IntMapDeserializerTest.scala | 1 + 3 files changed, 60 insertions(+), 79 deletions(-) delete mode 100644 src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer2.scala diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala b/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala index 3ad27b9db..bf43e4489 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala @@ -2,15 +2,13 @@ package com.fasterxml.jackson.module.scala.deser import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.databind.`type`.MapLikeType -import com.fasterxml.jackson.databind.deser.Deserializers -import com.fasterxml.jackson.databind.deser.std.{JsonNodeDeserializer, MapDeserializer, StdDeserializer, StdValueInstantiator} +import com.fasterxml.jackson.databind.deser.{ContextualDeserializer, Deserializers} +import com.fasterxml.jackson.databind.deser.std.{ContainerDeserializerBase, MapDeserializer, StdValueInstantiator} import com.fasterxml.jackson.databind.jsontype.TypeDeserializer -import com.fasterxml.jackson.databind.node.ObjectNode import com.fasterxml.jackson.databind._ import com.fasterxml.jackson.module.scala.{DefaultScalaModule, IteratorModule, JacksonModule} import java.util -import java.util.Map import scala.collection.immutable import scala.collection.immutable.IntMap import scala.languageFeature.postfixOps @@ -30,44 +28,76 @@ private object ImmutableIntMapDeserializerResolver extends Deserializers.Base { elementDeserializer: JsonDeserializer[_]): JsonDeserializer[_] = { if (!classOf[immutable.IntMap[_]].isAssignableFrom(theType.getRawClass)) None.orNull else { - new MapDeserializer(theType, new IntMapInstantiator(config, theType), keyDeserializer, + val mapDeserialiser = new MapDeserializer(theType, new IntMapInstantiator(config, theType), keyDeserializer, elementDeserializer.asInstanceOf[JsonDeserializer[AnyRef]], elementTypeDeserializer) + new IntMapDeserializer(theType, mapDeserialiser) } } - private class IntMapInstantiator(config: DeserializationConfig, mapType: MapLikeType) extends StdValueInstantiator(config, mapType) { - var intMap = IntMap[Any]() + private class IntMapDeserializer[V](mapType: MapLikeType, containerDeserializer: MapDeserializer) + extends ContainerDeserializerBase[IntMap[V]](mapType) with ContextualDeserializer { + + override def getContentType: JavaType = containerDeserializer.getContentType + + override def getContentDeserializer: JsonDeserializer[AnyRef] = containerDeserializer.getContentDeserializer + + override def createContextual(ctxt: DeserializationContext, property: BeanProperty): JsonDeserializer[_] = { + val newDelegate = containerDeserializer.createContextual(ctxt, property).asInstanceOf[MapDeserializer] + new IntMapDeserializer(mapType, newDelegate) + } + + override def deserialize(jp: JsonParser, ctxt: DeserializationContext): IntMap[V] = { + containerDeserializer.deserialize(jp, ctxt) match { + case wrapper: BuilderWrapper => wrapper.asIntMap() + } + } + + override def deserialize(jp: JsonParser, ctxt: DeserializationContext, intoValue: IntMap[V]): IntMap[V] = { + val newMap = deserialize(jp, ctxt) + if (newMap.isEmpty) { + intoValue + } else { + intoValue ++ newMap + } + } + + override def getEmptyValue(ctxt: DeserializationContext): Object = IntMap.empty[V] + } + private class IntMapInstantiator(config: DeserializationConfig, mapType: MapLikeType) extends StdValueInstantiator(config, mapType) { override def canCreateUsingDefault = true + override def createUsingDefault(ctxt: DeserializationContext) = new BuilderWrapper + } - override def createUsingDefault(ctxt: DeserializationContext) = new util.AbstractMap[Any, Any] { - override def put(k: Any, v: Any): Any = { - k match { - case i: Int => intMap += (i -> v) - case n: Number => intMap += (n.intValue() -> v) - case s: String => intMap += (s.toInt -> v) - case _ => { - val typeName = Option(k) match { - case Some(n) => n.getClass.getName - case _ => "null" - } - throw new IllegalArgumentException(s"IntMap does npt support keys of type $typeName") + private class BuilderWrapper extends util.AbstractMap[Any, Any] { + var intMap = IntMap[Any]() + + override def put(k: Any, v: Any): Any = { + k match { + case i: Int => intMap += (i -> v) + case n: Number => intMap += (n.intValue() -> v) + case s: String => intMap += (s.toInt -> v) + case _ => { + val typeName = Option(k) match { + case Some(n) => n.getClass.getName + case _ => "null" } + throw new IllegalArgumentException(s"IntMap does npt support keys of type $typeName") } - v - } - - // Used by the deserializer when using readerForUpdating - override def get(key: Any): Any = key match { - case i: Int => intMap.get(i).orNull - case _ => None.orNull } + v + } - // Isn't used by the deserializer - override def entrySet(): java.util.Set[java.util.Map.Entry[Any, Any]] = throw new UnsupportedOperationException + // Used by the deserializer when using readerForUpdating + override def get(key: Any): Any = key match { + case i: Int => intMap.get(i).orNull + case _ => None.orNull } - def asIntMap(): IntMap[_] = intMap + // Isn't used by the deserializer + override def entrySet(): java.util.Set[java.util.Map.Entry[Any, Any]] = throw new UnsupportedOperationException + + def asIntMap[V](): IntMap[V] = intMap.asInstanceOf[IntMap[V]] } } diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer2.scala b/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer2.scala deleted file mode 100644 index 4ecb66f49..000000000 --- a/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer2.scala +++ /dev/null @@ -1,50 +0,0 @@ -package com.fasterxml.jackson.module.scala.deser - -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.{BeanDescription, DeserializationConfig, DeserializationContext, JsonDeserializer, JsonNode, KeyDeserializer} -import com.fasterxml.jackson.databind.deser.std.{JsonNodeDeserializer, StdDeserializer} -import com.fasterxml.jackson.databind.`type`.MapLikeType -import com.fasterxml.jackson.databind.deser.Deserializers -import com.fasterxml.jackson.databind.jsontype.TypeDeserializer -import com.fasterxml.jackson.databind.node.ObjectNode -import com.fasterxml.jackson.module.scala.{DefaultScalaModule, IteratorModule} - -import scala.collection.immutable -import scala.languageFeature.postfixOps - -/** - * Adds support for deserializing Scala [[scala.collection.immutable.IntMap]]s. Scala IntMaps can already be - * serialized using [[IteratorModule]] or [[DefaultScalaModule]]. - * - * @since 2.14.0 - */ -private class ImmutableIntMapDeserializer2(elementDeserializer: JsonDeserializer[_]) - extends StdDeserializer[immutable.IntMap[_]](classOf[immutable.IntMap[_]]) { - override def deserialize(p: JsonParser, ctxt: DeserializationContext): immutable.IntMap[_] = { - val objectNodeDeserializer = JsonNodeDeserializer.getDeserializer(classOf[ObjectNode]) - val node = objectNodeDeserializer.deserialize(p, ctxt).asInstanceOf[ObjectNode] - val iter = node.fields() - var map = immutable.IntMap[String]() - while (iter.hasNext) { - val entry = iter.next() - //entry.getValue - //elementDeserializer. - map += (entry.getKey.toInt, entry.getValue.asText()) - } - map - } -} - -class ImmutableIntMapDeserializerResolver2 extends Deserializers.Base { - override def findMapLikeDeserializer(theType: MapLikeType, - config: DeserializationConfig, - beanDesc: BeanDescription, - keyDeserializer: KeyDeserializer, - elementTypeDeserializer: TypeDeserializer, - elementDeserializer: JsonDeserializer[_]): JsonDeserializer[_] = { - if (!classOf[immutable.IntMap[_]].isAssignableFrom(theType.getRawClass)) None.orNull - else { - new ImmutableIntMapDeserializer2(elementDeserializer) - } - } -} diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala index 7ad60ca8f..51daf389c 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala @@ -18,5 +18,6 @@ class IntMapDeserializerTest extends DeserializerTest { val read = mapper.readValue(json, new TypeReference[IntMap[Long]]{}) read shouldBe map + read.values.sum shouldEqual map.values.sum } } From 0dae5da20892b229af0124e44a5d24b23ada8944 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 20 Aug 2022 17:08:07 +0100 Subject: [PATCH 05/12] Update IntMapDeserializerTest.scala --- .../jackson/module/scala/deser/IntMapDeserializerTest.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala index 51daf389c..5edd78c20 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala @@ -10,14 +10,13 @@ class IntMapDeserializerTest extends DeserializerTest { def module: DefaultScalaModule.type = DefaultScalaModule "Scala Module" should "deserialize IntMap" in { - val map = IntMap(1 -> 100L, 2 -> 200L) + val map = IntMap(1 -> "one", 2 -> "two") val mapper = newBuilder.addModule(new IntMapDeserializerModule() {}).build() val json = mapper.writeValueAsString(map) - val read = mapper.readValue(json, new TypeReference[IntMap[Long]]{}) + val read = mapper.readValue(json, new TypeReference[IntMap[String]]{}) read shouldBe map - read.values.sum shouldEqual map.values.sum } } From c96f9f847f1b816155c857f822ba9c22e49a9031 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 20 Aug 2022 17:19:36 +0100 Subject: [PATCH 06/12] Update ImmutableIntMapDeserializer.scala --- .../scala/deser/ImmutableIntMapDeserializer.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala b/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala index bf43e4489..9745b6cb6 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala @@ -69,12 +69,11 @@ private object ImmutableIntMapDeserializerResolver extends Deserializers.Base { override def createUsingDefault(ctxt: DeserializationContext) = new BuilderWrapper } - private class BuilderWrapper extends util.AbstractMap[Any, Any] { - var intMap = IntMap[Any]() + private class BuilderWrapper extends util.AbstractMap[Object, Object] { + var intMap = IntMap[Object]() - override def put(k: Any, v: Any): Any = { + override def put(k: Object, v: Object): Object = { k match { - case i: Int => intMap += (i -> v) case n: Number => intMap += (n.intValue() -> v) case s: String => intMap += (s.toInt -> v) case _ => { @@ -89,13 +88,14 @@ private object ImmutableIntMapDeserializerResolver extends Deserializers.Base { } // Used by the deserializer when using readerForUpdating - override def get(key: Any): Any = key match { - case i: Int => intMap.get(i).orNull + override def get(key: Object): Object = key match { + case n: Number => intMap.get(n.intValue()).orNull + case s: String => intMap.get(s.toInt).orNull case _ => None.orNull } // Isn't used by the deserializer - override def entrySet(): java.util.Set[java.util.Map.Entry[Any, Any]] = throw new UnsupportedOperationException + override def entrySet(): java.util.Set[java.util.Map.Entry[Object, Object]] = throw new UnsupportedOperationException def asIntMap[V](): IntMap[V] = intMap.asInstanceOf[IntMap[V]] } From 18b2e44b578780e73026ca49891963a3a9d6b344 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 20 Aug 2022 17:37:35 +0100 Subject: [PATCH 07/12] long map --- .../deser/ImmutableIntMapDeserializer.scala | 31 +++--- .../deser/ImmutableLongMapDeserializer.scala | 105 ++++++++++++++++++ 2 files changed, 120 insertions(+), 16 deletions(-) create mode 100644 src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableLongMapDeserializer.scala diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala b/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala index 9745b6cb6..6a537d676 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala @@ -9,16 +9,9 @@ import com.fasterxml.jackson.databind._ import com.fasterxml.jackson.module.scala.{DefaultScalaModule, IteratorModule, JacksonModule} import java.util -import scala.collection.immutable import scala.collection.immutable.IntMap import scala.languageFeature.postfixOps -/** - * Adds support for deserializing Scala [[scala.collection.immutable.IntMap]]s. Scala IntMaps can already be - * serialized using [[IteratorModule]] or [[DefaultScalaModule]]. - * - * @since 2.14.0 - */ private object ImmutableIntMapDeserializerResolver extends Deserializers.Base { override def findMapLikeDeserializer(theType: MapLikeType, config: DeserializationConfig, @@ -26,11 +19,11 @@ private object ImmutableIntMapDeserializerResolver extends Deserializers.Base { keyDeserializer: KeyDeserializer, elementTypeDeserializer: TypeDeserializer, elementDeserializer: JsonDeserializer[_]): JsonDeserializer[_] = { - if (!classOf[immutable.IntMap[_]].isAssignableFrom(theType.getRawClass)) None.orNull + if (!classOf[IntMap[_]].isAssignableFrom(theType.getRawClass)) None.orNull else { - val mapDeserialiser = new MapDeserializer(theType, new IntMapInstantiator(config, theType), keyDeserializer, + val mapDeserializer = new MapDeserializer(theType, new IntMapInstantiator(config, theType), keyDeserializer, elementDeserializer.asInstanceOf[JsonDeserializer[AnyRef]], elementTypeDeserializer) - new IntMapDeserializer(theType, mapDeserialiser) + new IntMapDeserializer(theType, mapDeserializer) } } @@ -70,12 +63,12 @@ private object ImmutableIntMapDeserializerResolver extends Deserializers.Base { } private class BuilderWrapper extends util.AbstractMap[Object, Object] { - var intMap = IntMap[Object]() + var baseMap = IntMap[Object]() override def put(k: Object, v: Object): Object = { k match { - case n: Number => intMap += (n.intValue() -> v) - case s: String => intMap += (s.toInt -> v) + case n: Number => baseMap += (n.intValue() -> v) + case s: String => baseMap += (s.toInt -> v) case _ => { val typeName = Option(k) match { case Some(n) => n.getClass.getName @@ -89,18 +82,24 @@ private object ImmutableIntMapDeserializerResolver extends Deserializers.Base { // Used by the deserializer when using readerForUpdating override def get(key: Object): Object = key match { - case n: Number => intMap.get(n.intValue()).orNull - case s: String => intMap.get(s.toInt).orNull + case n: Number => baseMap.get(n.intValue()).orNull + case s: String => baseMap.get(s.toInt).orNull case _ => None.orNull } // Isn't used by the deserializer override def entrySet(): java.util.Set[java.util.Map.Entry[Object, Object]] = throw new UnsupportedOperationException - def asIntMap[V](): IntMap[V] = intMap.asInstanceOf[IntMap[V]] + def asIntMap[V](): IntMap[V] = baseMap.asInstanceOf[IntMap[V]] } } +/** + * Adds support for deserializing Scala [[scala.collection.immutable.IntMap]]s. Scala IntMaps can already be + * serialized using [[IteratorModule]] or [[DefaultScalaModule]]. + * + * @since 2.14.0 + */ trait IntMapDeserializerModule extends JacksonModule { this += ImmutableIntMapDeserializerResolver } diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableLongMapDeserializer.scala b/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableLongMapDeserializer.scala new file mode 100644 index 000000000..6a0d2a45c --- /dev/null +++ b/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableLongMapDeserializer.scala @@ -0,0 +1,105 @@ +package com.fasterxml.jackson.module.scala.deser + +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.`type`.MapLikeType +import com.fasterxml.jackson.databind.deser.{ContextualDeserializer, Deserializers} +import com.fasterxml.jackson.databind.deser.std.{ContainerDeserializerBase, MapDeserializer, StdValueInstantiator} +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer +import com.fasterxml.jackson.databind._ +import com.fasterxml.jackson.module.scala.{DefaultScalaModule, IteratorModule, JacksonModule} + +import java.util +import scala.collection.immutable +import scala.languageFeature.postfixOps + +private object ImmutableLongMapDeserializerResolver extends Deserializers.Base { + override def findMapLikeDeserializer(theType: MapLikeType, + config: DeserializationConfig, + beanDesc: BeanDescription, + keyDeserializer: KeyDeserializer, + elementTypeDeserializer: TypeDeserializer, + elementDeserializer: JsonDeserializer[_]): JsonDeserializer[_] = { + if (!classOf[immutable.LongMap[_]].isAssignableFrom(theType.getRawClass)) None.orNull + else { + val mapDeserializer = new MapDeserializer(theType, new ImmutableLongMapInstantiator(config, theType), keyDeserializer, + elementDeserializer.asInstanceOf[JsonDeserializer[AnyRef]], elementTypeDeserializer) + new ImmutableLongMapDeserializer(theType, mapDeserializer) + } + } + + private class ImmutableLongMapDeserializer[V](mapType: MapLikeType, containerDeserializer: MapDeserializer) + extends ContainerDeserializerBase[immutable.LongMap[V]](mapType) with ContextualDeserializer { + + override def getContentType: JavaType = containerDeserializer.getContentType + + override def getContentDeserializer: JsonDeserializer[AnyRef] = containerDeserializer.getContentDeserializer + + override def createContextual(ctxt: DeserializationContext, property: BeanProperty): JsonDeserializer[_] = { + val newDelegate = containerDeserializer.createContextual(ctxt, property).asInstanceOf[MapDeserializer] + new ImmutableLongMapDeserializer(mapType, newDelegate) + } + + override def deserialize(jp: JsonParser, ctxt: DeserializationContext): immutable.LongMap[V] = { + containerDeserializer.deserialize(jp, ctxt) match { + case wrapper: BuilderWrapper => wrapper.asLongMap() + } + } + + override def deserialize(jp: JsonParser, ctxt: DeserializationContext, intoValue: immutable.LongMap[V]): immutable.LongMap[V] = { + val newMap = deserialize(jp, ctxt) + if (newMap.isEmpty) { + intoValue + } else { + intoValue ++ newMap + } + } + + override def getEmptyValue(ctxt: DeserializationContext): Object = immutable.LongMap.empty[V] + } + + private class ImmutableLongMapInstantiator(config: DeserializationConfig, mapType: MapLikeType) extends StdValueInstantiator(config, mapType) { + override def canCreateUsingDefault = true + override def createUsingDefault(ctxt: DeserializationContext) = new BuilderWrapper + } + + private class BuilderWrapper extends util.AbstractMap[Object, Object] { + var baseMap = immutable.LongMap[Object]() + + override def put(k: Object, v: Object): Object = { + k match { + case n: Number => baseMap += (n.longValue() -> v) + case s: String => baseMap += (s.toLong -> v) + case _ => { + val typeName = Option(k) match { + case Some(n) => n.getClass.getName + case _ => "null" + } + throw new IllegalArgumentException(s"IntMap does npt support keys of type $typeName") + } + } + v + } + + // Used by the deserializer when using readerForUpdating + override def get(key: Object): Object = key match { + case n: Number => baseMap.get(n.longValue()).orNull + case s: String => baseMap.get(s.toInt).orNull + case _ => None.orNull + } + + // Isn't used by the deserializer + override def entrySet(): java.util.Set[java.util.Map.Entry[Object, Object]] = throw new UnsupportedOperationException + + def asLongMap[V](): immutable.LongMap[V] = baseMap.asInstanceOf[immutable.LongMap[V]] + } +} + +/** + * Adds support for deserializing Scala [[scala.collection.immutable.LongMap]]s. Scala LongMaps can already be + * serialized using [[IteratorModule]] or [[DefaultScalaModule]]. + * + * @since 2.14.0 + */ +trait LongMapDeserializerModule extends JacksonModule { + this += ImmutableLongMapDeserializerResolver +} From bdea35283763ef3f5eca112baf3a64e8c8ef8b69 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 20 Aug 2022 17:50:24 +0100 Subject: [PATCH 08/12] wip --- ...r.scala => IntMapDeserializerModule.scala} | 0 ....scala => LongMapDeserializerModule.scala} | 89 +++++++++++++++++-- .../scala/deser/LongMapDeserializerTest.scala | 33 +++++++ 3 files changed, 114 insertions(+), 8 deletions(-) rename src/main/scala/com/fasterxml/jackson/module/scala/deser/{ImmutableIntMapDeserializer.scala => IntMapDeserializerModule.scala} (100%) rename src/main/scala/com/fasterxml/jackson/module/scala/deser/{ImmutableLongMapDeserializer.scala => LongMapDeserializerModule.scala} (54%) create mode 100644 src/test/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerTest.scala diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala b/src/main/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerModule.scala similarity index 100% rename from src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableIntMapDeserializer.scala rename to src/main/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerModule.scala diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableLongMapDeserializer.scala b/src/main/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerModule.scala similarity index 54% rename from src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableLongMapDeserializer.scala rename to src/main/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerModule.scala index 6a0d2a45c..94ccc5206 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/deser/ImmutableLongMapDeserializer.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerModule.scala @@ -9,21 +9,26 @@ import com.fasterxml.jackson.databind._ import com.fasterxml.jackson.module.scala.{DefaultScalaModule, IteratorModule, JacksonModule} import java.util -import scala.collection.immutable +import scala.collection.{immutable, mutable} import scala.languageFeature.postfixOps -private object ImmutableLongMapDeserializerResolver extends Deserializers.Base { +private object LongMapDeserializerResolver extends Deserializers.Base { override def findMapLikeDeserializer(theType: MapLikeType, config: DeserializationConfig, beanDesc: BeanDescription, keyDeserializer: KeyDeserializer, elementTypeDeserializer: TypeDeserializer, elementDeserializer: JsonDeserializer[_]): JsonDeserializer[_] = { - if (!classOf[immutable.LongMap[_]].isAssignableFrom(theType.getRawClass)) None.orNull - else { + if (classOf[immutable.LongMap[_]].isAssignableFrom(theType.getRawClass)) { val mapDeserializer = new MapDeserializer(theType, new ImmutableLongMapInstantiator(config, theType), keyDeserializer, elementDeserializer.asInstanceOf[JsonDeserializer[AnyRef]], elementTypeDeserializer) new ImmutableLongMapDeserializer(theType, mapDeserializer) + } else if (classOf[mutable.LongMap[_]].isAssignableFrom(theType.getRawClass)) { + val mapDeserializer = new MapDeserializer(theType, new MutableLongMapInstantiator(config, theType), keyDeserializer, + elementDeserializer.asInstanceOf[JsonDeserializer[AnyRef]], elementTypeDeserializer) + new MutableLongMapDeserializer(theType, mapDeserializer) + } else { + None.orNull } } @@ -41,7 +46,7 @@ private object ImmutableLongMapDeserializerResolver extends Deserializers.Base { override def deserialize(jp: JsonParser, ctxt: DeserializationContext): immutable.LongMap[V] = { containerDeserializer.deserialize(jp, ctxt) match { - case wrapper: BuilderWrapper => wrapper.asLongMap() + case wrapper: ImmutableMapWrapper => wrapper.asLongMap() } } @@ -57,12 +62,48 @@ private object ImmutableLongMapDeserializerResolver extends Deserializers.Base { override def getEmptyValue(ctxt: DeserializationContext): Object = immutable.LongMap.empty[V] } + private class MutableLongMapDeserializer[V](mapType: MapLikeType, containerDeserializer: MapDeserializer) + extends ContainerDeserializerBase[mutable.LongMap[V]](mapType) with ContextualDeserializer { + + override def getContentType: JavaType = containerDeserializer.getContentType + + override def getContentDeserializer: JsonDeserializer[AnyRef] = containerDeserializer.getContentDeserializer + + override def createContextual(ctxt: DeserializationContext, property: BeanProperty): JsonDeserializer[_] = { + val newDelegate = containerDeserializer.createContextual(ctxt, property).asInstanceOf[MapDeserializer] + new MutableLongMapDeserializer(mapType, newDelegate) + } + + override def deserialize(jp: JsonParser, ctxt: DeserializationContext): mutable.LongMap[V] = { + containerDeserializer.deserialize(jp, ctxt) match { + case wrapper: MutableMapWrapper => wrapper.asLongMap() + } + } + + override def deserialize(jp: JsonParser, ctxt: DeserializationContext, intoValue: mutable.LongMap[V]): mutable.LongMap[V] = { + val newMap = deserialize(jp, ctxt) + if (newMap.isEmpty) { + intoValue + } else { + intoValue ++ newMap + } + } + + override def getEmptyValue(ctxt: DeserializationContext): Object = mutable.LongMap.empty[V] + } + private class ImmutableLongMapInstantiator(config: DeserializationConfig, mapType: MapLikeType) extends StdValueInstantiator(config, mapType) { override def canCreateUsingDefault = true - override def createUsingDefault(ctxt: DeserializationContext) = new BuilderWrapper + override def createUsingDefault(ctxt: DeserializationContext) = new ImmutableMapWrapper } - private class BuilderWrapper extends util.AbstractMap[Object, Object] { + private class MutableLongMapInstantiator(config: DeserializationConfig, mapType: MapLikeType) extends StdValueInstantiator(config, mapType) { + override def canCreateUsingDefault = true + + override def createUsingDefault(ctxt: DeserializationContext) = new MutableMapWrapper + } + + private class ImmutableMapWrapper extends util.AbstractMap[Object, Object] { var baseMap = immutable.LongMap[Object]() override def put(k: Object, v: Object): Object = { @@ -92,6 +133,38 @@ private object ImmutableLongMapDeserializerResolver extends Deserializers.Base { def asLongMap[V](): immutable.LongMap[V] = baseMap.asInstanceOf[immutable.LongMap[V]] } + + private class MutableMapWrapper extends util.AbstractMap[Object, Object] { + var baseMap = mutable.LongMap[Object]() + + override def put(k: Object, v: Object): Object = { + k match { + case n: Number => baseMap += (n.longValue() -> v) + case s: String => baseMap += (s.toLong -> v) + case _ => { + val typeName = Option(k) match { + case Some(n) => n.getClass.getName + case _ => "null" + } + throw new IllegalArgumentException(s"IntMap does npt support keys of type $typeName") + } + } + v + } + + // Used by the deserializer when using readerForUpdating + override def get(key: Object): Object = key match { + case n: Number => baseMap.get(n.longValue()).orNull + case s: String => baseMap.get(s.toInt).orNull + case _ => None.orNull + } + + // Isn't used by the deserializer + override def entrySet(): java.util.Set[java.util.Map.Entry[Object, Object]] = throw new UnsupportedOperationException + + def asLongMap[V](): mutable.LongMap[V] = baseMap.asInstanceOf[mutable.LongMap[V]] + } + } /** @@ -101,5 +174,5 @@ private object ImmutableLongMapDeserializerResolver extends Deserializers.Base { * @since 2.14.0 */ trait LongMapDeserializerModule extends JacksonModule { - this += ImmutableLongMapDeserializerResolver + this += LongMapDeserializerResolver } diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerTest.scala new file mode 100644 index 000000000..6ee3a7cc1 --- /dev/null +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerTest.scala @@ -0,0 +1,33 @@ +package com.fasterxml.jackson.module.scala.deser + +import com.fasterxml.jackson.core.`type`.TypeReference +import com.fasterxml.jackson.module.scala.DefaultScalaModule + +import scala.collection.{immutable, mutable} + +class LongMapDeserializerTest extends DeserializerTest { + + def module: DefaultScalaModule.type = DefaultScalaModule + + "Scala Module" should "deserialize immutable LongMap" in { + val map = immutable.LongMap(1L -> "one", 2L -> "two") + + val mapper = newBuilder.addModule(new LongMapDeserializerModule() {}).build() + + val json = mapper.writeValueAsString(map) + val read = mapper.readValue(json, new TypeReference[immutable.LongMap[String]] {}) + + read shouldBe map + } + + it should "deserialize mutable LongMap" in { + val map = mutable.LongMap(1L -> "one", 2L -> "two") + + val mapper = newBuilder.addModule(new LongMapDeserializerModule() {}).build() + + val json = mapper.writeValueAsString(map) + val read = mapper.readValue(json, new TypeReference[mutable.LongMap[String]] {}) + + read shouldBe map + } +} From f48633c5420ebe83958c0eb852648605ba253c41 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 20 Aug 2022 18:56:11 +0100 Subject: [PATCH 09/12] debugging --- .../scala/deser/IntMapDeserializerModule.scala | 5 +++-- .../scala/deser/LongMapDeserializerModule.scala | 8 +++++--- .../module/scala/deser/IntMapDeserializerTest.scala | 12 ++++++++++++ .../module/scala/deser/LongMapDeserializerTest.scala | 4 ++-- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerModule.scala b/src/main/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerModule.scala index 6a537d676..51de8f03e 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerModule.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerModule.scala @@ -9,8 +9,8 @@ import com.fasterxml.jackson.databind._ import com.fasterxml.jackson.module.scala.{DefaultScalaModule, IteratorModule, JacksonModule} import java.util +import scala.collection.JavaConverters._ import scala.collection.immutable.IntMap -import scala.languageFeature.postfixOps private object ImmutableIntMapDeserializerResolver extends Deserializers.Base { override def findMapLikeDeserializer(theType: MapLikeType, @@ -88,7 +88,8 @@ private object ImmutableIntMapDeserializerResolver extends Deserializers.Base { } // Isn't used by the deserializer - override def entrySet(): java.util.Set[java.util.Map.Entry[Object, Object]] = throw new UnsupportedOperationException + override def entrySet(): java.util.Set[java.util.Map.Entry[Object, Object]] = + baseMap.asJava.entrySet().asInstanceOf[java.util.Set[java.util.Map.Entry[Object, Object]]] def asIntMap[V](): IntMap[V] = baseMap.asInstanceOf[IntMap[V]] } diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerModule.scala b/src/main/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerModule.scala index 94ccc5206..f5f265b25 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerModule.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerModule.scala @@ -10,7 +10,7 @@ import com.fasterxml.jackson.module.scala.{DefaultScalaModule, IteratorModule, J import java.util import scala.collection.{immutable, mutable} -import scala.languageFeature.postfixOps +import scala.collection.JavaConverters._ private object LongMapDeserializerResolver extends Deserializers.Base { override def findMapLikeDeserializer(theType: MapLikeType, @@ -129,7 +129,8 @@ private object LongMapDeserializerResolver extends Deserializers.Base { } // Isn't used by the deserializer - override def entrySet(): java.util.Set[java.util.Map.Entry[Object, Object]] = throw new UnsupportedOperationException + override def entrySet(): java.util.Set[java.util.Map.Entry[Object, Object]] = + baseMap.asJava.entrySet().asInstanceOf[java.util.Set[java.util.Map.Entry[Object, Object]]] def asLongMap[V](): immutable.LongMap[V] = baseMap.asInstanceOf[immutable.LongMap[V]] } @@ -160,7 +161,8 @@ private object LongMapDeserializerResolver extends Deserializers.Base { } // Isn't used by the deserializer - override def entrySet(): java.util.Set[java.util.Map.Entry[Object, Object]] = throw new UnsupportedOperationException + override def entrySet(): java.util.Set[java.util.Map.Entry[Object, Object]] = + baseMap.asJava.entrySet().asInstanceOf[java.util.Set[java.util.Map.Entry[Object, Object]]] def asLongMap[V](): mutable.LongMap[V] = baseMap.asInstanceOf[mutable.LongMap[V]] } diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala index 5edd78c20..7198298de 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala @@ -19,4 +19,16 @@ class IntMapDeserializerTest extends DeserializerTest { read shouldBe map } + + it should "deserialize IntMap (long value)" in { + val map = IntMap(1 -> 100L, 2 -> 200L) + + val mapper = newBuilder.addModule(new IntMapDeserializerModule() {}).build() + + val json = mapper.writeValueAsString(map) + val read = mapper.readValue(json, new TypeReference[IntMap[Long]] {}) + + read shouldEqual map + read.values.sum shouldEqual map.values.sum + } } diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerTest.scala index 6ee3a7cc1..fca19ac3c 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerTest.scala @@ -17,7 +17,7 @@ class LongMapDeserializerTest extends DeserializerTest { val json = mapper.writeValueAsString(map) val read = mapper.readValue(json, new TypeReference[immutable.LongMap[String]] {}) - read shouldBe map + read shouldEqual map } it should "deserialize mutable LongMap" in { @@ -28,6 +28,6 @@ class LongMapDeserializerTest extends DeserializerTest { val json = mapper.writeValueAsString(map) val read = mapper.readValue(json, new TypeReference[mutable.LongMap[String]] {}) - read shouldBe map + read shouldEqual map } } From 233b5c795dc6be16bf648582e2139c259fdc532d Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 20 Aug 2022 19:20:28 +0100 Subject: [PATCH 10/12] Update ScalaTypeModifier.scala --- .../scala/modifiers/ScalaTypeModifier.scala | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/modifiers/ScalaTypeModifier.scala b/src/main/scala/com/fasterxml/jackson/module/scala/modifiers/ScalaTypeModifier.scala index 930571491..f34151806 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/modifiers/ScalaTypeModifier.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/modifiers/ScalaTypeModifier.scala @@ -1,18 +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`._ import com.fasterxml.jackson.module.scala.JacksonModule +import java.lang.reflect.Type import scala.collection._ +import scala.collection.immutable.IntMap class ScalaTypeModifier extends TypeModifier { private val optionClass = classOf[Option[_]] private val eitherClass = classOf[Either[_, _]] private val mapClass = classOf[Map[_, _]] + private val intClass = classOf[Int] + private val intMapClass = classOf[IntMap[_]] + private val longClass = classOf[Long] + private val immutableLongMapClass = classOf[immutable.LongMap[_]] + private val mutableLongMapClass = classOf[mutable.LongMap[_]] private val iterableOnceClass = classOf[IterableOnce[_]] override def modifyType(javaType: JavaType, @@ -27,7 +32,13 @@ class ScalaTypeModifier extends TypeModifier { case _ => ReferenceType.upgradeFrom(javaType, javaType.containedTypeOrUnknown(0)) } } else if (javaType.isTypeOrSubTypeOf(mapClass)) { - MapLikeType.upgradeFrom(javaType, javaType.containedTypeOrUnknown(0), javaType.containedTypeOrUnknown(1)) + if (javaType.isTypeOrSubTypeOf(intMapClass)) { + MapLikeType.upgradeFrom(javaType, typeFactory.constructType(intClass), javaType.containedTypeOrUnknown(0)) + } else if (javaType.isTypeOrSubTypeOf(immutableLongMapClass) || javaType.isTypeOrSubTypeOf(mutableLongMapClass)) { + MapLikeType.upgradeFrom(javaType, typeFactory.constructType(longClass), javaType.containedTypeOrUnknown(0)) + } else { + MapLikeType.upgradeFrom(javaType, javaType.containedTypeOrUnknown(0), javaType.containedTypeOrUnknown(1)) + } } else if (javaType.isTypeOrSubTypeOf(iterableOnceClass)) { CollectionLikeType.upgradeFrom(javaType, javaType.containedTypeOrUnknown(0)) } else if (javaType.isTypeOrSubTypeOf(eitherClass)) { From e21c73cb2ea4a16db141f2459c5475c8cb9592fa Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 20 Aug 2022 19:30:28 +0100 Subject: [PATCH 11/12] Update IntMapDeserializerTest.scala --- .../scala/deser/IntMapDeserializerTest.scala | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala index 7198298de..7751469cb 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala @@ -2,9 +2,15 @@ package com.fasterxml.jackson.module.scala.deser import com.fasterxml.jackson.core.`type`.TypeReference import com.fasterxml.jackson.module.scala.DefaultScalaModule +import com.fasterxml.jackson.module.scala.deser.IntMapDeserializerTest.IntMapWrapper +import com.fasterxml.jackson.module.scala.introspect.ScalaAnnotationIntrospectorModule import scala.collection.immutable.IntMap +object IntMapDeserializerTest { + case class IntMapWrapper(values: IntMap[Long]) +} + class IntMapDeserializerTest extends DeserializerTest { def module: DefaultScalaModule.type = DefaultScalaModule @@ -28,6 +34,33 @@ class IntMapDeserializerTest extends DeserializerTest { val json = mapper.writeValueAsString(map) val read = mapper.readValue(json, new TypeReference[IntMap[Long]] {}) + read shouldEqual map + // next line fails due to type erasure (values are ints and won't cast to longs) + //read.values.sum shouldEqual map.values.sum + } + + it should "deserialize IntMapWrapper" in { + val map = IntMap(1 -> 100L, 2 -> 200L) + val instance = IntMapWrapper(map) + + val mapper = newBuilder.addModule(new IntMapDeserializerModule() {}).build() + + val json = mapper.writeValueAsString(instance) + val read = mapper.readValue(json, classOf[IntMapWrapper]) + + read shouldEqual instance + // next line fails due to type erasure (values are ints and won't cast to longs) + //read.values.values.sum shouldEqual map.values.sum + } + + it should "deserialize IntMap (bigint value)" in { + val map = IntMap(1 -> 100L, 2 -> 200L) + + val mapper = newBuilder.addModule(new IntMapDeserializerModule() {}).build() + + val json = mapper.writeValueAsString(map) + val read = mapper.readValue(json, new TypeReference[IntMap[BigInt]] {}) + read shouldEqual map read.values.sum shouldEqual map.values.sum } From d94074338ab4c0a43e967b692952dacce806bbac Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 20 Aug 2022 19:57:19 +0100 Subject: [PATCH 12/12] refactor --- .../deser/UnsortedMapDeserializerModule.scala | 22 +++++++++++++++ .../deser/UnsortedMapDeserializerModule.scala | 22 +++++++++++++++ .../deser/UnsortedMapDeserializerModule.scala | 22 +++++++++++++++ ...cala => IntMapDeserializerReseolver.scala} | 25 ++++++++--------- ...cala => LongMapDeserializerResolver.scala} | 28 +++++++++---------- .../scala/deser/IntMapDeserializerTest.scala | 9 +++--- .../scala/deser/LongMapDeserializerTest.scala | 28 +++++++++++++++++-- 7 files changed, 122 insertions(+), 34 deletions(-) rename src/main/scala/com/fasterxml/jackson/module/scala/deser/{IntMapDeserializerModule.scala => IntMapDeserializerReseolver.scala} (93%) rename src/main/scala/com/fasterxml/jackson/module/scala/deser/{LongMapDeserializerModule.scala => LongMapDeserializerResolver.scala} (93%) diff --git a/src/main/scala-2.11/com/fasterxml/jackson/module/scala/deser/UnsortedMapDeserializerModule.scala b/src/main/scala-2.11/com/fasterxml/jackson/module/scala/deser/UnsortedMapDeserializerModule.scala index ceabcaab2..bca4f81a3 100644 --- a/src/main/scala-2.11/com/fasterxml/jackson/module/scala/deser/UnsortedMapDeserializerModule.scala +++ b/src/main/scala-2.11/com/fasterxml/jackson/module/scala/deser/UnsortedMapDeserializerModule.scala @@ -1,6 +1,8 @@ package com.fasterxml.jackson.module.scala.deser import com.fasterxml.jackson.databind._ +import com.fasterxml.jackson.databind.`type`.MapLikeType +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer import com.fasterxml.jackson.module.scala.modifiers.MapTypeModifierModule import scala.collection._ @@ -27,5 +29,25 @@ trait UnsortedMapDeserializerModule extends MapTypeModifierModule { )) override def builderFor[K, V](factory: Factory, keyType: JavaType, valueType: JavaType): Builder[K, V] = factory.newBuilder[K, V] + + override def findMapLikeDeserializer(theType: MapLikeType, + config: DeserializationConfig, + beanDesc: BeanDescription, + keyDeserializer: KeyDeserializer, + elementTypeDeserializer: TypeDeserializer, + elementDeserializer: JsonDeserializer[_]): JsonDeserializer[_] = { + + var deserializer = LongMapDeserializerResolver.findMapLikeDeserializer( + theType, config, beanDesc, keyDeserializer, elementTypeDeserializer, elementDeserializer) + if (deserializer == null) { + deserializer = IntMapDeserializerResolver.findMapLikeDeserializer( + theType, config, beanDesc, keyDeserializer, elementTypeDeserializer, elementDeserializer) + if (deserializer == null) { + deserializer = super.findMapLikeDeserializer( + theType, config, beanDesc, keyDeserializer, elementTypeDeserializer, elementDeserializer) + } + } + deserializer + } }) } diff --git a/src/main/scala-2.12/com/fasterxml/jackson/module/scala/deser/UnsortedMapDeserializerModule.scala b/src/main/scala-2.12/com/fasterxml/jackson/module/scala/deser/UnsortedMapDeserializerModule.scala index ceabcaab2..bca4f81a3 100644 --- a/src/main/scala-2.12/com/fasterxml/jackson/module/scala/deser/UnsortedMapDeserializerModule.scala +++ b/src/main/scala-2.12/com/fasterxml/jackson/module/scala/deser/UnsortedMapDeserializerModule.scala @@ -1,6 +1,8 @@ package com.fasterxml.jackson.module.scala.deser import com.fasterxml.jackson.databind._ +import com.fasterxml.jackson.databind.`type`.MapLikeType +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer import com.fasterxml.jackson.module.scala.modifiers.MapTypeModifierModule import scala.collection._ @@ -27,5 +29,25 @@ trait UnsortedMapDeserializerModule extends MapTypeModifierModule { )) override def builderFor[K, V](factory: Factory, keyType: JavaType, valueType: JavaType): Builder[K, V] = factory.newBuilder[K, V] + + override def findMapLikeDeserializer(theType: MapLikeType, + config: DeserializationConfig, + beanDesc: BeanDescription, + keyDeserializer: KeyDeserializer, + elementTypeDeserializer: TypeDeserializer, + elementDeserializer: JsonDeserializer[_]): JsonDeserializer[_] = { + + var deserializer = LongMapDeserializerResolver.findMapLikeDeserializer( + theType, config, beanDesc, keyDeserializer, elementTypeDeserializer, elementDeserializer) + if (deserializer == null) { + deserializer = IntMapDeserializerResolver.findMapLikeDeserializer( + theType, config, beanDesc, keyDeserializer, elementTypeDeserializer, elementDeserializer) + if (deserializer == null) { + deserializer = super.findMapLikeDeserializer( + theType, config, beanDesc, keyDeserializer, elementTypeDeserializer, elementDeserializer) + } + } + deserializer + } }) } diff --git a/src/main/scala-2.13/com/fasterxml/jackson/module/scala/deser/UnsortedMapDeserializerModule.scala b/src/main/scala-2.13/com/fasterxml/jackson/module/scala/deser/UnsortedMapDeserializerModule.scala index f4c4e50eb..b3ca4fe51 100644 --- a/src/main/scala-2.13/com/fasterxml/jackson/module/scala/deser/UnsortedMapDeserializerModule.scala +++ b/src/main/scala-2.13/com/fasterxml/jackson/module/scala/deser/UnsortedMapDeserializerModule.scala @@ -1,6 +1,8 @@ package com.fasterxml.jackson.module.scala.deser import com.fasterxml.jackson.databind._ +import com.fasterxml.jackson.databind.`type`.MapLikeType +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer import com.fasterxml.jackson.module.scala.modifiers.MapTypeModifierModule import scala.collection._ @@ -26,5 +28,25 @@ trait UnsortedMapDeserializerModule extends MapTypeModifierModule { )) override def builderFor[K, V](factory: Factory, keyType: JavaType, valueType: JavaType): Builder[K, V] = factory.newBuilder[K, V] + + override def findMapLikeDeserializer(theType: MapLikeType, + config: DeserializationConfig, + beanDesc: BeanDescription, + keyDeserializer: KeyDeserializer, + elementTypeDeserializer: TypeDeserializer, + elementDeserializer: JsonDeserializer[_]): JsonDeserializer[_] = { + + var deserializer = LongMapDeserializerResolver.findMapLikeDeserializer( + theType, config, beanDesc, keyDeserializer, elementTypeDeserializer, elementDeserializer) + if (deserializer == null) { + deserializer = IntMapDeserializerResolver.findMapLikeDeserializer( + theType, config, beanDesc, keyDeserializer, elementTypeDeserializer, elementDeserializer) + if (deserializer == null) { + deserializer = super.findMapLikeDeserializer( + theType, config, beanDesc, keyDeserializer, elementTypeDeserializer, elementDeserializer) + } + } + deserializer + } }) } diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerModule.scala b/src/main/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerReseolver.scala similarity index 93% rename from src/main/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerModule.scala rename to src/main/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerReseolver.scala index 51de8f03e..5947fed4d 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerModule.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerReseolver.scala @@ -6,20 +6,29 @@ import com.fasterxml.jackson.databind.deser.{ContextualDeserializer, Deserialize import com.fasterxml.jackson.databind.deser.std.{ContainerDeserializerBase, MapDeserializer, StdValueInstantiator} import com.fasterxml.jackson.databind.jsontype.TypeDeserializer import com.fasterxml.jackson.databind._ -import com.fasterxml.jackson.module.scala.{DefaultScalaModule, IteratorModule, JacksonModule} +import com.fasterxml.jackson.module.scala.{DefaultScalaModule, IteratorModule} import java.util import scala.collection.JavaConverters._ import scala.collection.immutable.IntMap -private object ImmutableIntMapDeserializerResolver extends Deserializers.Base { +/** + * Adds support for deserializing Scala [[scala.collection.immutable.IntMap]]s. Scala IntMaps can already be + * serialized using [[IteratorModule]] or [[DefaultScalaModule]]. + * + * @since 2.14.0 + */ +private[deser] object IntMapDeserializerResolver extends Deserializers.Base { + + private val intMapClass = classOf[IntMap[_]] + override def findMapLikeDeserializer(theType: MapLikeType, config: DeserializationConfig, beanDesc: BeanDescription, keyDeserializer: KeyDeserializer, elementTypeDeserializer: TypeDeserializer, elementDeserializer: JsonDeserializer[_]): JsonDeserializer[_] = { - if (!classOf[IntMap[_]].isAssignableFrom(theType.getRawClass)) None.orNull + if (!intMapClass.isAssignableFrom(theType.getRawClass)) None.orNull else { val mapDeserializer = new MapDeserializer(theType, new IntMapInstantiator(config, theType), keyDeserializer, elementDeserializer.asInstanceOf[JsonDeserializer[AnyRef]], elementTypeDeserializer) @@ -94,13 +103,3 @@ private object ImmutableIntMapDeserializerResolver extends Deserializers.Base { def asIntMap[V](): IntMap[V] = baseMap.asInstanceOf[IntMap[V]] } } - -/** - * Adds support for deserializing Scala [[scala.collection.immutable.IntMap]]s. Scala IntMaps can already be - * serialized using [[IteratorModule]] or [[DefaultScalaModule]]. - * - * @since 2.14.0 - */ -trait IntMapDeserializerModule extends JacksonModule { - this += ImmutableIntMapDeserializerResolver -} diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerModule.scala b/src/main/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerResolver.scala similarity index 93% rename from src/main/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerModule.scala rename to src/main/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerResolver.scala index f5f265b25..cd8c781be 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerModule.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerResolver.scala @@ -6,24 +6,34 @@ import com.fasterxml.jackson.databind.deser.{ContextualDeserializer, Deserialize import com.fasterxml.jackson.databind.deser.std.{ContainerDeserializerBase, MapDeserializer, StdValueInstantiator} import com.fasterxml.jackson.databind.jsontype.TypeDeserializer import com.fasterxml.jackson.databind._ -import com.fasterxml.jackson.module.scala.{DefaultScalaModule, IteratorModule, JacksonModule} +import com.fasterxml.jackson.module.scala.{DefaultScalaModule, IteratorModule} import java.util import scala.collection.{immutable, mutable} import scala.collection.JavaConverters._ -private object LongMapDeserializerResolver extends Deserializers.Base { +/** + * Adds support for deserializing Scala [[scala.collection.immutable.LongMap]]s and [[scala.collection.mutable.LongMap]]s. + * Scala LongMaps can already be serialized using [[IteratorModule]] or [[DefaultScalaModule]]. + * + * @since 2.14.0 + */ +private[deser] object LongMapDeserializerResolver extends Deserializers.Base { + + private val immutableLongMapClass = classOf[immutable.LongMap[_]] + private val mutableLongMapClass = classOf[mutable.LongMap[_]] + override def findMapLikeDeserializer(theType: MapLikeType, config: DeserializationConfig, beanDesc: BeanDescription, keyDeserializer: KeyDeserializer, elementTypeDeserializer: TypeDeserializer, elementDeserializer: JsonDeserializer[_]): JsonDeserializer[_] = { - if (classOf[immutable.LongMap[_]].isAssignableFrom(theType.getRawClass)) { + if (immutableLongMapClass.isAssignableFrom(theType.getRawClass)) { val mapDeserializer = new MapDeserializer(theType, new ImmutableLongMapInstantiator(config, theType), keyDeserializer, elementDeserializer.asInstanceOf[JsonDeserializer[AnyRef]], elementTypeDeserializer) new ImmutableLongMapDeserializer(theType, mapDeserializer) - } else if (classOf[mutable.LongMap[_]].isAssignableFrom(theType.getRawClass)) { + } else if (mutableLongMapClass.isAssignableFrom(theType.getRawClass)) { val mapDeserializer = new MapDeserializer(theType, new MutableLongMapInstantiator(config, theType), keyDeserializer, elementDeserializer.asInstanceOf[JsonDeserializer[AnyRef]], elementTypeDeserializer) new MutableLongMapDeserializer(theType, mapDeserializer) @@ -168,13 +178,3 @@ private object LongMapDeserializerResolver extends Deserializers.Base { } } - -/** - * Adds support for deserializing Scala [[scala.collection.immutable.LongMap]]s. Scala LongMaps can already be - * serialized using [[IteratorModule]] or [[DefaultScalaModule]]. - * - * @since 2.14.0 - */ -trait LongMapDeserializerModule extends JacksonModule { - this += LongMapDeserializerResolver -} diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala index 7751469cb..43e73ef2e 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/IntMapDeserializerTest.scala @@ -3,7 +3,6 @@ package com.fasterxml.jackson.module.scala.deser import com.fasterxml.jackson.core.`type`.TypeReference import com.fasterxml.jackson.module.scala.DefaultScalaModule import com.fasterxml.jackson.module.scala.deser.IntMapDeserializerTest.IntMapWrapper -import com.fasterxml.jackson.module.scala.introspect.ScalaAnnotationIntrospectorModule import scala.collection.immutable.IntMap @@ -18,7 +17,7 @@ class IntMapDeserializerTest extends DeserializerTest { "Scala Module" should "deserialize IntMap" in { val map = IntMap(1 -> "one", 2 -> "two") - val mapper = newBuilder.addModule(new IntMapDeserializerModule() {}).build() + val mapper = newMapper val json = mapper.writeValueAsString(map) val read = mapper.readValue(json, new TypeReference[IntMap[String]]{}) @@ -29,7 +28,7 @@ class IntMapDeserializerTest extends DeserializerTest { it should "deserialize IntMap (long value)" in { val map = IntMap(1 -> 100L, 2 -> 200L) - val mapper = newBuilder.addModule(new IntMapDeserializerModule() {}).build() + val mapper = newMapper val json = mapper.writeValueAsString(map) val read = mapper.readValue(json, new TypeReference[IntMap[Long]] {}) @@ -43,7 +42,7 @@ class IntMapDeserializerTest extends DeserializerTest { val map = IntMap(1 -> 100L, 2 -> 200L) val instance = IntMapWrapper(map) - val mapper = newBuilder.addModule(new IntMapDeserializerModule() {}).build() + val mapper = newMapper val json = mapper.writeValueAsString(instance) val read = mapper.readValue(json, classOf[IntMapWrapper]) @@ -56,7 +55,7 @@ class IntMapDeserializerTest extends DeserializerTest { it should "deserialize IntMap (bigint value)" in { val map = IntMap(1 -> 100L, 2 -> 200L) - val mapper = newBuilder.addModule(new IntMapDeserializerModule() {}).build() + val mapper = newMapper val json = mapper.writeValueAsString(map) val read = mapper.readValue(json, new TypeReference[IntMap[BigInt]] {}) diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerTest.scala index fca19ac3c..bcc1adae6 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/LongMapDeserializerTest.scala @@ -12,7 +12,7 @@ class LongMapDeserializerTest extends DeserializerTest { "Scala Module" should "deserialize immutable LongMap" in { val map = immutable.LongMap(1L -> "one", 2L -> "two") - val mapper = newBuilder.addModule(new LongMapDeserializerModule() {}).build() + val mapper = newMapper val json = mapper.writeValueAsString(map) val read = mapper.readValue(json, new TypeReference[immutable.LongMap[String]] {}) @@ -20,14 +20,38 @@ class LongMapDeserializerTest extends DeserializerTest { read shouldEqual map } + it should "deserialize immutable LongMap (bigint)" in { + val map = immutable.LongMap(1L -> 100, 2L -> 200) + + val mapper = newMapper + + val json = mapper.writeValueAsString(map) + val read = mapper.readValue(json, new TypeReference[immutable.LongMap[BigInt]] {}) + + read shouldEqual map + read.values.sum shouldEqual map.values.sum + } + it should "deserialize mutable LongMap" in { val map = mutable.LongMap(1L -> "one", 2L -> "two") - val mapper = newBuilder.addModule(new LongMapDeserializerModule() {}).build() + val mapper = newMapper val json = mapper.writeValueAsString(map) val read = mapper.readValue(json, new TypeReference[mutable.LongMap[String]] {}) read shouldEqual map } + + it should "deserialize mutable LongMap (bigint)" in { + val map = mutable.LongMap(1L -> 100, 2L -> 200) + + val mapper = newMapper + + val json = mapper.writeValueAsString(map) + val read = mapper.readValue(json, new TypeReference[mutable.LongMap[BigInt]] {}) + + read shouldEqual map + read.values.sum shouldEqual map.values.sum + } }