Skip to content

Int/long map deserialization #595

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Aug 20, 2022
Original file line number Diff line number Diff line change
@@ -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._
Expand All @@ -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
}
})
}
Original file line number Diff line number Diff line change
@@ -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._
Expand All @@ -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
}
})
}
Original file line number Diff line number Diff line change
@@ -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._
Expand All @@ -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
}
})
}
Original file line number Diff line number Diff line change
@@ -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}

import java.util
import scala.collection.JavaConverters._
import scala.collection.immutable.IntMap

/**
* 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 (!intMapClass.isAssignableFrom(theType.getRawClass)) None.orNull
else {
val mapDeserializer = new MapDeserializer(theType, new IntMapInstantiator(config, theType), keyDeserializer,
elementDeserializer.asInstanceOf[JsonDeserializer[AnyRef]], elementTypeDeserializer)
new IntMapDeserializer(theType, mapDeserializer)
}
}

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
}

private class BuilderWrapper extends util.AbstractMap[Object, Object] {
var baseMap = IntMap[Object]()

override def put(k: Object, v: Object): Object = {
k match {
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
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.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]] =
baseMap.asJava.entrySet().asInstanceOf[java.util.Set[java.util.Map.Entry[Object, Object]]]

def asIntMap[V](): IntMap[V] = baseMap.asInstanceOf[IntMap[V]]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
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}

import java.util
import scala.collection.{immutable, mutable}
import scala.collection.JavaConverters._

/**
* 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 (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 (mutableLongMapClass.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
}
}

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: ImmutableMapWrapper => 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 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 ImmutableMapWrapper
}

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 = {
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]] =
baseMap.asJava.entrySet().asInstanceOf[java.util.Set[java.util.Map.Entry[Object, Object]]]

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]] =
baseMap.asJava.entrySet().asInstanceOf[java.util.Set[java.util.Map.Entry[Object, Object]]]

def asLongMap[V](): mutable.LongMap[V] = baseMap.asInstanceOf[mutable.LongMap[V]]
}

}
Loading