From 0d86c3aa225677b591469d22fdda82471d8a691a Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 20 Sep 2021 15:17:56 +0100 Subject: [PATCH 01/18] wip --- .../scala/introspect/BeanIntrospector.scala | 26 +++++++++++++++--- .../ScalaAnnotationIntrospectorModule.scala | 27 +++++++++++-------- .../OptionWithNumberDeserializerTest.scala | 8 +++--- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/BeanIntrospector.scala b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/BeanIntrospector.scala index 94faf3c54..554743f9a 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/BeanIntrospector.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/BeanIntrospector.scala @@ -37,10 +37,9 @@ import scala.reflect.NameTransformer object BeanIntrospector { - private def getCtorParams(ctor: Constructor[_]): Seq[String] = { - val names = JavaParameterIntrospector.getCtorParamNames(ctor) - names.map(NameTransformer.decode) - } + private case class FieldKey(clazz: Class[_], fieldName: String) + + private val overrideMap = scala.collection.mutable.Map[FieldKey, Class[_]]() def apply[T <: AnyRef](cls: Class[_]) = { @@ -246,4 +245,23 @@ object BeanIntrospector { BeanDescriptor(cls, fields ++ methods ++ lazyValMethods) } + + /** + * jackson-module-scala does not always properly handle deserialization of Options or Collections wrapping + * Scala primitives (eg Int, Long, Boolean). There are general issues with serializing and deserializing + * Scala 2 Enumerations. These issues can be worked around by adding Jackson annotations on the affected fields. + * This function is designed to be used when it is not possible to apply Jackson annotations. + * + * @param clazz + * @param fieldName + * @param referencedType + */ + def registerReferencedType(clazz: Class[_], fieldName: String, referencedType: Class[_]): Unit = { + overrideMap.update(FieldKey(clazz, fieldName), referencedType) + } + + private def getCtorParams(ctor: Constructor[_]): Seq[String] = { + val names = JavaParameterIntrospector.getCtorParamNames(ctor) + names.map(NameTransformer.decode) + } } diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala index f04d08256..ef53c6c13 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala @@ -109,22 +109,27 @@ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueI val applyDefaultValues = config.isEnabled(MapperFeature.APPLY_DEFAULT_VALUES) val args = delegate.getFromObjectArguments(config) Option(args) match { - case Some(array) if applyDefaultValues => { + case Some(array) => { array.map { - case creator: CreatorProperty => - // Locate the constructor param that matches it - descriptor.properties.find(_.param.exists(_.index == creator.getCreatorIndex)) match { - case Some(PropertyDescriptor(name, Some(ConstructorParameter(_, _, Some(defaultValue))), _, _, _, _, _)) => - creator.withNullProvider(new NullValueProvider { - override def getNullValue(ctxt: DeserializationContext): AnyRef = defaultValue() - override def getNullAccessPattern: AccessPattern = AccessPattern.DYNAMIC - }) - case _ => creator + case creator: CreatorProperty => { + if (applyDefaultValues) { + // Locate the constructor param that matches it + descriptor.properties.find(_.param.exists(_.index == creator.getCreatorIndex)) match { + case Some(PropertyDescriptor(name, Some(ConstructorParameter(_, _, Some(defaultValue))), _, _, _, _, _)) => + creator.withNullProvider(new NullValueProvider { + override def getNullValue(ctxt: DeserializationContext): AnyRef = defaultValue() + + override def getNullAccessPattern: AccessPattern = AccessPattern.DYNAMIC + }) + case _ => creator + } + } else { + creator } + } case other => other } } - case Some(array) => array case _ => Array.empty } } diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala index 0192a1eb7..709827db8 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala @@ -2,6 +2,7 @@ package com.fasterxml.jackson.module.scala.deser import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.module.scala.DefaultScalaModule +import com.fasterxml.jackson.module.scala.introspect.BeanIntrospector object OptionWithNumberDeserializerTest { case class AnnotatedOptionLong(@JsonDeserialize(contentAs = classOf[java.lang.Long]) valueLong: Option[Long]) @@ -34,12 +35,13 @@ class OptionWithNumberDeserializerTest extends DeserializerTest { } it should "support OptionLong" in { + BeanIntrospector.registerReferencedType(classOf[OptionLong], "valueLong", classOf[Long]) val v1 = deserialize("""{"valueLong":151}""", classOf[OptionLong]) v1 shouldBe OptionLong(Some(151L)) v1.valueLong.get shouldBe 151L - //next assert fails due to unboxing issue -- without the @JsonDeserialize to help, jackson will - //erroneously create an Option[Int] instead of Option[Long] leading to a class cast exception - //useOptionLong(v1.valueLong) shouldBe 302L + //this will next call will fail with a Scala unboxing exception unless you BeanIntrospector.registerReferencedType + //or use one of the equivalent classes in OptionWithNumberDeserializerTest + useOptionLong(v1.valueLong) shouldBe 302L } it should "support OptionJavaLong" in { From 66019a2fe1ea7045f979f8d4d02f6ca3ebae17f5 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 20 Sep 2021 15:39:41 +0100 Subject: [PATCH 02/18] wip --- .../scala/introspect/BeanIntrospector.scala | 18 ------ .../ScalaAnnotationIntrospectorModule.scala | 56 ++++++++++++++----- .../OptionWithNumberDeserializerTest.scala | 12 +++- 3 files changed, 51 insertions(+), 35 deletions(-) diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/BeanIntrospector.scala b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/BeanIntrospector.scala index 554743f9a..fca0f028b 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/BeanIntrospector.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/BeanIntrospector.scala @@ -37,10 +37,6 @@ import scala.reflect.NameTransformer object BeanIntrospector { - private case class FieldKey(clazz: Class[_], fieldName: String) - - private val overrideMap = scala.collection.mutable.Map[FieldKey, Class[_]]() - def apply[T <: AnyRef](cls: Class[_]) = { /** @@ -246,20 +242,6 @@ object BeanIntrospector { BeanDescriptor(cls, fields ++ methods ++ lazyValMethods) } - /** - * jackson-module-scala does not always properly handle deserialization of Options or Collections wrapping - * Scala primitives (eg Int, Long, Boolean). There are general issues with serializing and deserializing - * Scala 2 Enumerations. These issues can be worked around by adding Jackson annotations on the affected fields. - * This function is designed to be used when it is not possible to apply Jackson annotations. - * - * @param clazz - * @param fieldName - * @param referencedType - */ - def registerReferencedType(clazz: Class[_], fieldName: String, referencedType: Class[_]): Unit = { - overrideMap.update(FieldKey(clazz, fieldName), referencedType) - } - private def getCtorParams(ctor: Constructor[_]): Seq[String] = { val names = JavaParameterIntrospector.getCtorParamNames(ctor) names.map(NameTransformer.decode) diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala index ef53c6c13..b33a507c9 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala @@ -16,6 +16,28 @@ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueI private [this] var _descriptorCache: LookupCache[ClassKey, BeanDescriptor] = new LRUMap[ClassKey, BeanDescriptor](16, 100) + private case class FieldKey(clazz: Class[_], fieldName: String) + + private val overrideMap = scala.collection.mutable.Map[FieldKey, Class[_]]() + + /** + * jackson-module-scala does not always properly handle deserialization of Options or Collections wrapping + * Scala primitives (eg Int, Long, Boolean). There are general issues with serializing and deserializing + * Scala 2 Enumerations. These issues can be worked around by adding Jackson annotations on the affected fields. + * This function is designed to be used when it is not possible to apply Jackson annotations. + * + * @param clazz + * @param fieldName + * @param referencedType + */ + def registerReferencedType(clazz: Class[_], fieldName: String, referencedType: Class[_]): Unit = { + overrideMap.update(FieldKey(clazz, fieldName), referencedType) + } + + def clearRegisteredReferencedTypes(): Unit = { + overrideMap.clear() + } + def setDescriptorCache(cache: LookupCache[ClassKey, BeanDescriptor]): LookupCache[ClassKey, BeanDescriptor] = { val existingCache = _descriptorCache _descriptorCache = cache @@ -109,27 +131,33 @@ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueI val applyDefaultValues = config.isEnabled(MapperFeature.APPLY_DEFAULT_VALUES) val args = delegate.getFromObjectArguments(config) Option(args) match { - case Some(array) => { + case Some(array) if (applyDefaultValues || overrideMap.nonEmpty) => { array.map { case creator: CreatorProperty => { - if (applyDefaultValues) { - // Locate the constructor param that matches it - descriptor.properties.find(_.param.exists(_.index == creator.getCreatorIndex)) match { - case Some(PropertyDescriptor(name, Some(ConstructorParameter(_, _, Some(defaultValue))), _, _, _, _, _)) => - creator.withNullProvider(new NullValueProvider { - override def getNullValue(ctxt: DeserializationContext): AnyRef = defaultValue() - - override def getNullAccessPattern: AccessPattern = AccessPattern.DYNAMIC - }) - case _ => creator + // Locate the constructor param that matches it + descriptor.properties.find(_.param.exists(_.index == creator.getCreatorIndex)) match { + case Some(pd) => { + if (applyDefaultValues) { + pd match { + case PropertyDescriptor(_, Some(ConstructorParameter(_, _, Some(defaultValue))), _, _, _, _, _) => { + creator.withNullProvider(new NullValueProvider { + override def getNullValue(ctxt: DeserializationContext): AnyRef = defaultValue() + + override def getNullAccessPattern: AccessPattern = AccessPattern.DYNAMIC + }) + } + case _ => creator + } + } else { + creator + } } - } else { - creator + case _ => creator } } - case other => other } } + case Some(array) => array case _ => Array.empty } } diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala index 709827db8..1ce3d803b 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala @@ -2,7 +2,8 @@ package com.fasterxml.jackson.module.scala.deser import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.module.scala.DefaultScalaModule -import com.fasterxml.jackson.module.scala.introspect.BeanIntrospector +import com.fasterxml.jackson.module.scala.introspect.ScalaAnnotationIntrospector +import org.scalatest.BeforeAndAfterEach object OptionWithNumberDeserializerTest { case class AnnotatedOptionLong(@JsonDeserialize(contentAs = classOf[java.lang.Long]) valueLong: Option[Long]) @@ -12,7 +13,7 @@ object OptionWithNumberDeserializerTest { case class OptionBigInt(value: Option[BigInt]) } -class OptionWithNumberDeserializerTest extends DeserializerTest { +class OptionWithNumberDeserializerTest extends DeserializerTest with BeforeAndAfterEach { lazy val module: DefaultScalaModule.type = DefaultScalaModule import OptionWithNumberDeserializerTest._ @@ -20,6 +21,11 @@ class OptionWithNumberDeserializerTest extends DeserializerTest { private def useOptionJavaLong(v: Option[java.lang.Long]): Long = v.map(_ * 2).getOrElse(0L) private def useOptionBigInt(v: Option[BigInt]): Long = v.map(_ * 2).map(_.toLong).getOrElse(0L) + override def afterEach(): Unit = { + super.afterEach() + ScalaAnnotationIntrospector.clearRegisteredReferencedTypes() + } + "JacksonModuleScala" should "support AnnotatedOptionLong" in { val v1 = deserialize("""{"valueLong":151}""", classOf[AnnotatedOptionLong]) v1 shouldBe AnnotatedOptionLong(Some(151L)) @@ -35,7 +41,7 @@ class OptionWithNumberDeserializerTest extends DeserializerTest { } it should "support OptionLong" in { - BeanIntrospector.registerReferencedType(classOf[OptionLong], "valueLong", classOf[Long]) + ScalaAnnotationIntrospector.registerReferencedType(classOf[OptionLong], "valueLong", classOf[Long]) val v1 = deserialize("""{"valueLong":151}""", classOf[OptionLong]) v1 shouldBe OptionLong(Some(151L)) v1.valueLong.get shouldBe 151L From 9a8a00a52c3b6b84331f0c66eab31db37f2354d5 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 20 Sep 2021 16:20:12 +0100 Subject: [PATCH 03/18] Update ScalaAnnotationIntrospectorModule.scala --- .../ScalaAnnotationIntrospectorModule.scala | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala index b33a507c9..29016950e 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala @@ -1,6 +1,5 @@ package com.fasterxml.jackson.module.scala.introspect -import java.lang.annotation.Annotation import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.databind.`type`.ClassKey import com.fasterxml.jackson.databind.cfg.MapperConfig @@ -8,17 +7,20 @@ import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator import com.fasterxml.jackson.databind.deser._ import com.fasterxml.jackson.databind.introspect._ import com.fasterxml.jackson.databind.util.{AccessPattern, LRUMap, LookupCache} -import com.fasterxml.jackson.databind.{BeanDescription, DeserializationConfig, DeserializationContext, DeserializationFeature, MapperFeature} +import com.fasterxml.jackson.databind.{BeanDescription, DeserializationConfig, DeserializationContext, MapperFeature} import com.fasterxml.jackson.module.scala.JacksonModule import com.fasterxml.jackson.module.scala.util.Implicits._ +import java.lang.annotation.Annotation +import scala.collection.JavaConverters._ + object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueInstantiators { private [this] var _descriptorCache: LookupCache[ClassKey, BeanDescriptor] = new LRUMap[ClassKey, BeanDescriptor](16, 100) - private case class FieldKey(clazz: Class[_], fieldName: String) + private case class ClassOverrides(overrides: scala.collection.mutable.Map[String, Class[_]] = scala.collection.mutable.Map.empty) - private val overrideMap = scala.collection.mutable.Map[FieldKey, Class[_]]() + private val overrideMap = scala.collection.mutable.Map[Class[_], ClassOverrides]() /** * jackson-module-scala does not always properly handle deserialization of Options or Collections wrapping @@ -31,7 +33,7 @@ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueI * @param referencedType */ def registerReferencedType(clazz: Class[_], fieldName: String, referencedType: Class[_]): Unit = { - overrideMap.update(FieldKey(clazz, fieldName), referencedType) + overrideMap.getOrElseUpdate(clazz, ClassOverrides()).overrides.update(fieldName, referencedType) } def clearRegisteredReferencedTypes(): Unit = { @@ -128,15 +130,17 @@ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueI extends StdValueInstantiator(delegate) { private val overriddenConstructorArguments: Array[SettableBeanProperty] = { + val overrides = overrideMap.get(descriptor.beanType).map(_.overrides.toMap).getOrElse(Map.empty) val applyDefaultValues = config.isEnabled(MapperFeature.APPLY_DEFAULT_VALUES) val args = delegate.getFromObjectArguments(config) Option(args) match { - case Some(array) if (applyDefaultValues || overrideMap.nonEmpty) => { + case Some(array) if (applyDefaultValues || overrides.nonEmpty) => { array.map { case creator: CreatorProperty => { // Locate the constructor param that matches it descriptor.properties.find(_.param.exists(_.index == creator.getCreatorIndex)) match { case Some(pd) => { + println(s">>>>> override ${pd.name} ${overrides.get(pd.name)}") if (applyDefaultValues) { pd match { case PropertyDescriptor(_, Some(ConstructorParameter(_, _, Some(defaultValue))), _, _, _, _, _) => { @@ -173,7 +177,7 @@ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueI if (isMaybeScalaBeanType(beanDesc.getBeanClass)) { _descriptorFor(beanDesc.getBeanClass).map { descriptor => - if (descriptor.properties.exists(_.param.exists(_.defaultValue.isDefined))) { + if (overrideMap.contains(beanDesc.getBeanClass) || descriptor.properties.exists(_.param.exists(_.defaultValue.isDefined))) { defaultInstantiator match { case std: StdValueInstantiator => new ScalaValueInstantiator(std, config, descriptor) From 890e656e14d7ecc1c40565381ee69c1763760be2 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 20 Sep 2021 16:56:36 +0100 Subject: [PATCH 04/18] Update ScalaAnnotationIntrospectorModule.scala --- .../ScalaAnnotationIntrospectorModule.scala | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala index 29016950e..d51e40a31 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala @@ -2,17 +2,17 @@ package com.fasterxml.jackson.module.scala.introspect import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.databind.`type`.ClassKey +import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.cfg.MapperConfig import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator import com.fasterxml.jackson.databind.deser._ import com.fasterxml.jackson.databind.introspect._ -import com.fasterxml.jackson.databind.util.{AccessPattern, LRUMap, LookupCache} -import com.fasterxml.jackson.databind.{BeanDescription, DeserializationConfig, DeserializationContext, MapperFeature} +import com.fasterxml.jackson.databind.util.{AccessPattern, Converter, LRUMap, LookupCache} +import com.fasterxml.jackson.databind.{BeanDescription, DeserializationConfig, DeserializationContext, JsonDeserializer, KeyDeserializer, MapperFeature} import com.fasterxml.jackson.module.scala.JacksonModule import com.fasterxml.jackson.module.scala.util.Implicits._ import java.lang.annotation.Annotation -import scala.collection.JavaConverters._ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueInstantiators { private [this] var _descriptorCache: LookupCache[ClassKey, BeanDescriptor] = @@ -140,20 +140,23 @@ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueI // Locate the constructor param that matches it descriptor.properties.find(_.param.exists(_.index == creator.getCreatorIndex)) match { case Some(pd) => { - println(s">>>>> override ${pd.name} ${overrides.get(pd.name)}") + val mappedCreator = overrides.get(pd.name) match { + case Some(refClass) => WrappedCreatorProperty(creator, refClass) + case _ => creator + } if (applyDefaultValues) { pd match { case PropertyDescriptor(_, Some(ConstructorParameter(_, _, Some(defaultValue))), _, _, _, _, _) => { - creator.withNullProvider(new NullValueProvider { + mappedCreator.withNullProvider(new NullValueProvider { override def getNullValue(ctxt: DeserializationContext): AnyRef = defaultValue() override def getNullAccessPattern: AccessPattern = AccessPattern.DYNAMIC }) } - case _ => creator + case _ => mappedCreator } } else { - creator + mappedCreator } } case _ => creator @@ -250,3 +253,30 @@ trait ScalaAnnotationIntrospectorModule extends JacksonModule { this += { _.appendAnnotationIntrospector(ScalaAnnotationIntrospector) } this += { _.addValueInstantiators(ScalaAnnotationIntrospector) } } + +private case class WrappedCreatorProperty(creatorProperty: CreatorProperty, refClass: Class[_]) + extends CreatorProperty(creatorProperty, creatorProperty.getFullName) { + + override def getAnnotation[A <: Annotation](acls: Class[A]): A = { + val result = Option(super.getAnnotation(acls)) match { + case None if acls.isAssignableFrom(classOf[JsonDeserialize]) => Some(getInstanceOfContentAsAnnotation()) + case result => result + } + result.orNull.asInstanceOf[A] + } + + private def getInstanceOfContentAsAnnotation(): JsonDeserialize = { + new JsonDeserialize() { + override def contentAs: Class[_] = refClass + override def annotationType: Class[JsonDeserialize] = classOf[JsonDeserialize] + override def as(): Class[_] = classOf[Void] + override def keyAs(): Class[_] = classOf[Void] + override def builder(): Class[_] = classOf[Void] + override def contentConverter(): Class[_ <: Converter[_, _]] = classOf[Converter.None] + override def converter(): Class[_ <: Converter[_, _]] = classOf[Converter.None] + override def using(): Class[_ <: JsonDeserializer[_]] = classOf[JsonDeserializer.None] + override def contentUsing(): Class[_ <: JsonDeserializer[_]] = classOf[JsonDeserializer.None] + override def keyUsing(): Class[_ <: KeyDeserializer] = classOf[KeyDeserializer.None] + } + } +} From b9b4c6bf4c18468cd86a9864067dbf0e39eef303 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 20 Sep 2021 17:19:23 +0100 Subject: [PATCH 05/18] Update ScalaAnnotationIntrospectorModule.scala --- .../ScalaAnnotationIntrospectorModule.scala | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala index d51e40a31..112beeb39 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala @@ -1,14 +1,14 @@ package com.fasterxml.jackson.module.scala.introspect import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.databind.`type`.ClassKey +import com.fasterxml.jackson.databind.`type`.{ClassKey, ReferenceType, SimpleType} import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.cfg.MapperConfig import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator import com.fasterxml.jackson.databind.deser._ import com.fasterxml.jackson.databind.introspect._ import com.fasterxml.jackson.databind.util.{AccessPattern, Converter, LRUMap, LookupCache} -import com.fasterxml.jackson.databind.{BeanDescription, DeserializationConfig, DeserializationContext, JsonDeserializer, KeyDeserializer, MapperFeature} +import com.fasterxml.jackson.databind.{BeanDescription, DeserializationConfig, DeserializationContext, JavaType, JsonDeserializer, KeyDeserializer, MapperFeature} import com.fasterxml.jackson.module.scala.JacksonModule import com.fasterxml.jackson.module.scala.util.Implicits._ @@ -265,7 +265,22 @@ private case class WrappedCreatorProperty(creatorProperty: CreatorProperty, refC result.orNull.asInstanceOf[A] } - private def getInstanceOfContentAsAnnotation(): JsonDeserialize = { + override def getContextAnnotation[A <: Annotation](acls: Class[A]): A = { + val result = Option(super.getContextAnnotation(acls)) match { + case None if acls.isAssignableFrom(classOf[JsonDeserialize]) => Some(getInstanceOfContentAsAnnotation()) + case result => result + } + result.orNull.asInstanceOf[A] + } + + override def getType: JavaType = { + super.getType match { + case rt: ReferenceType => ReferenceType.upgradeFrom(rt, SimpleType.constructUnsafe(refClass)) + case other => other + } + } + + private def getInstanceOfContentAsAnnotation() = { new JsonDeserialize() { override def contentAs: Class[_] = refClass override def annotationType: Class[JsonDeserialize] = classOf[JsonDeserialize] From 8132d0a6350ee463a9065c9da7b5585e40a0d394 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 20 Sep 2021 17:45:55 +0100 Subject: [PATCH 06/18] Update ScalaAnnotationIntrospectorModule.scala --- .../ScalaAnnotationIntrospectorModule.scala | 68 ++++++++----------- 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala index 112beeb39..6902320f3 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala @@ -2,13 +2,12 @@ package com.fasterxml.jackson.module.scala.introspect import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.databind.`type`.{ClassKey, ReferenceType, SimpleType} -import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.cfg.MapperConfig import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator import com.fasterxml.jackson.databind.deser._ import com.fasterxml.jackson.databind.introspect._ -import com.fasterxml.jackson.databind.util.{AccessPattern, Converter, LRUMap, LookupCache} -import com.fasterxml.jackson.databind.{BeanDescription, DeserializationConfig, DeserializationContext, JavaType, JsonDeserializer, KeyDeserializer, MapperFeature} +import com.fasterxml.jackson.databind.util.{AccessPattern, LRUMap, LookupCache} +import com.fasterxml.jackson.databind.{BeanDescription, DeserializationConfig, DeserializationContext, JavaType, MapperFeature} import com.fasterxml.jackson.module.scala.JacksonModule import com.fasterxml.jackson.module.scala.util.Implicits._ @@ -25,17 +24,41 @@ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueI /** * jackson-module-scala does not always properly handle deserialization of Options or Collections wrapping * Scala primitives (eg Int, Long, Boolean). There are general issues with serializing and deserializing - * Scala 2 Enumerations. These issues can be worked around by adding Jackson annotations on the affected fields. + * Scala 2 Enumerations. This function will not help with Enumerations. + *

+ * This function is experimental and may be removed or significantly reworked in a later release. + *

+ * These issues can be worked around by adding Jackson annotations on the affected fields. * This function is designed to be used when it is not possible to apply Jackson annotations. * - * @param clazz - * @param fieldName - * @param referencedType + * @param clazz the (case) class + * @param fieldName the field name in the (case) class + * @param referencedType the referenced type of the field - for `Option[Long]` - the referenced type is `Long` + * @see [[clearRegisteredReferencedTypes()]] + * @see [[clearRegisteredReferencedTypes(Class[_])]] + * @since 2.13.0 */ def registerReferencedType(clazz: Class[_], fieldName: String, referencedType: Class[_]): Unit = { overrideMap.getOrElseUpdate(clazz, ClassOverrides()).overrides.update(fieldName, referencedType) } + /** + * clears the state associated with reference types for the given class + * @param clazz the class for which to remove the registered reference types + * @see [[registerReferencedType]] + * @see [[clearRegisteredReferencedTypes()]] + * @since 2.13.0 + */ + def clearRegisteredReferencedTypes(clazz: Class[_]): Unit = { + overrideMap.remove(clazz) + } + + /** + * clears all the state associated with reference types + * @see [[registerReferencedType]] + * @see [[clearRegisteredReferencedTypes(Class[_])]] + * @since 2.13.0 + */ def clearRegisteredReferencedTypes(): Unit = { overrideMap.clear() } @@ -257,41 +280,10 @@ trait ScalaAnnotationIntrospectorModule extends JacksonModule { private case class WrappedCreatorProperty(creatorProperty: CreatorProperty, refClass: Class[_]) extends CreatorProperty(creatorProperty, creatorProperty.getFullName) { - override def getAnnotation[A <: Annotation](acls: Class[A]): A = { - val result = Option(super.getAnnotation(acls)) match { - case None if acls.isAssignableFrom(classOf[JsonDeserialize]) => Some(getInstanceOfContentAsAnnotation()) - case result => result - } - result.orNull.asInstanceOf[A] - } - - override def getContextAnnotation[A <: Annotation](acls: Class[A]): A = { - val result = Option(super.getContextAnnotation(acls)) match { - case None if acls.isAssignableFrom(classOf[JsonDeserialize]) => Some(getInstanceOfContentAsAnnotation()) - case result => result - } - result.orNull.asInstanceOf[A] - } - override def getType: JavaType = { super.getType match { case rt: ReferenceType => ReferenceType.upgradeFrom(rt, SimpleType.constructUnsafe(refClass)) case other => other } } - - private def getInstanceOfContentAsAnnotation() = { - new JsonDeserialize() { - override def contentAs: Class[_] = refClass - override def annotationType: Class[JsonDeserialize] = classOf[JsonDeserialize] - override def as(): Class[_] = classOf[Void] - override def keyAs(): Class[_] = classOf[Void] - override def builder(): Class[_] = classOf[Void] - override def contentConverter(): Class[_ <: Converter[_, _]] = classOf[Converter.None] - override def converter(): Class[_ <: Converter[_, _]] = classOf[Converter.None] - override def using(): Class[_ <: JsonDeserializer[_]] = classOf[JsonDeserializer.None] - override def contentUsing(): Class[_ <: JsonDeserializer[_]] = classOf[JsonDeserializer.None] - override def keyUsing(): Class[_ <: KeyDeserializer] = classOf[KeyDeserializer.None] - } - } } From 3cf503dde15220e64d9e5a052d024a3d4163e81b Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 20 Sep 2021 17:52:46 +0100 Subject: [PATCH 07/18] Update OptionWithNumberDeserializerTest.scala --- .../module/scala/deser/OptionWithNumberDeserializerTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala index 1ce3d803b..6e042c972 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala @@ -45,7 +45,7 @@ class OptionWithNumberDeserializerTest extends DeserializerTest with BeforeAndAf val v1 = deserialize("""{"valueLong":151}""", classOf[OptionLong]) v1 shouldBe OptionLong(Some(151L)) v1.valueLong.get shouldBe 151L - //this will next call will fail with a Scala unboxing exception unless you BeanIntrospector.registerReferencedType + //this will next call will fail with a Scala unboxing exception unless you ScalaAnnotationIntrospector.registerReferencedType //or use one of the equivalent classes in OptionWithNumberDeserializerTest useOptionLong(v1.valueLong) shouldBe 302L } From d6fcc0d4e808479558a1f642506623826390649b Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 20 Sep 2021 19:55:38 +0100 Subject: [PATCH 08/18] Create SeqWithNumberDeserializerTest.scala --- .../deser/SeqWithNumberDeserializerTest.scala | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/test/scala/com/fasterxml/jackson/module/scala/deser/SeqWithNumberDeserializerTest.scala diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/SeqWithNumberDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/SeqWithNumberDeserializerTest.scala new file mode 100644 index 000000000..ad7f15819 --- /dev/null +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/SeqWithNumberDeserializerTest.scala @@ -0,0 +1,61 @@ +package com.fasterxml.jackson.module.scala.deser + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import com.fasterxml.jackson.module.scala.DefaultScalaModule +import com.fasterxml.jackson.module.scala.introspect.ScalaAnnotationIntrospector +import org.scalatest.BeforeAndAfterEach + +object SeqWithNumberDeserializerTest { + case class AnnotatedSeqLong(@JsonDeserialize(contentAs = classOf[java.lang.Long]) longs: Seq[Long]) + case class AnnotatedSeqPrimitiveLong(@JsonDeserialize(contentAs = classOf[Long]) longs: Seq[Long]) + case class SeqLong(longs: Seq[Long]) + case class SeqJavaLong(longs: Seq[java.lang.Long]) + case class SeqBigInt(longs: Seq[BigInt]) +} + +class SeqWithNumberDeserializerTest extends DeserializerTest with BeforeAndAfterEach { + lazy val module: DefaultScalaModule.type = DefaultScalaModule + import SeqWithNumberDeserializerTest._ + + private def sumSeqLong(v: Seq[Long]): Long = v.sum + private def sumSeqJavaLong(v: Seq[java.lang.Long]): Long = v.map(_.toLong).sum + private def sumSeqBigInt(v: Seq[BigInt]): Long = v.sum.toLong + + override def afterEach(): Unit = { + super.afterEach() + ScalaAnnotationIntrospector.clearRegisteredReferencedTypes() + } + + "JacksonModuleScala" should "support AnnotatedSeqLong" in { + val v1 = deserialize("""{"longs":[151,152,153]}""", classOf[AnnotatedSeqLong]) + v1 shouldBe AnnotatedSeqLong(Seq(151L, 152L, 153L)) + sumSeqLong(v1.longs) shouldBe 456L + } + + it should "support AnnotatedSeqPrimitiveLong" in { + val v1 = deserialize("""{"longs":[151,152,153]}""", classOf[AnnotatedSeqPrimitiveLong]) + v1 shouldBe AnnotatedSeqPrimitiveLong(Seq(151L, 152L, 153L)) + sumSeqLong(v1.longs) shouldBe 456L + } + + it should "support SeqLong" in { + ScalaAnnotationIntrospector.registerReferencedType(classOf[SeqLong], "longs", classOf[Long]) + val v1 = deserialize("""{"longs":[151,152,153]}""", classOf[SeqLong]) + v1 shouldBe SeqLong(Seq(151L, 152L, 153L)) + //this will next call will fail with a Scala unboxing exception unless you ScalaAnnotationIntrospector.registerReferencedType + //or use one of the equivalent classes in SeqWithNumberDeserializerTest + sumSeqLong(v1.longs) shouldBe 456L + } + + it should "support SeqJavaLong" in { + val v1 = deserialize("""{"longs":[151,152,153]}""", classOf[SeqJavaLong]) + v1 shouldBe SeqJavaLong(Seq(151L, 152L, 153L)) + sumSeqJavaLong(v1.longs) shouldBe 456L + } + + it should "support SeqBigInt" in { + val v1 = deserialize("""{"longs":[151,152,153]}""", classOf[SeqBigInt]) + v1 shouldBe SeqBigInt(Seq(151L, 152L, 153L)) + sumSeqBigInt(v1.longs) shouldBe 456L + } +} From 4fe136e61fd509eb50679cf1c3974e2fce066d6d Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 20 Sep 2021 20:07:27 +0100 Subject: [PATCH 09/18] Update ScalaAnnotationIntrospectorModule.scala --- .../scala/introspect/ScalaAnnotationIntrospectorModule.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala index 6902320f3..0eced7895 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala @@ -1,7 +1,7 @@ package com.fasterxml.jackson.module.scala.introspect import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.databind.`type`.{ClassKey, ReferenceType, SimpleType} +import com.fasterxml.jackson.databind.`type`.{ClassKey, CollectionLikeType, CollectionType, ReferenceType, SimpleType} import com.fasterxml.jackson.databind.cfg.MapperConfig import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator import com.fasterxml.jackson.databind.deser._ @@ -283,6 +283,7 @@ private case class WrappedCreatorProperty(creatorProperty: CreatorProperty, refC override def getType: JavaType = { super.getType match { case rt: ReferenceType => ReferenceType.upgradeFrom(rt, SimpleType.constructUnsafe(refClass)) + case ct: CollectionLikeType => CollectionLikeType.upgradeFrom(ct, SimpleType.constructUnsafe(refClass)) case other => other } } From 886722c74bf969a1b5a0ba8780261d1bbeb55944 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 20 Sep 2021 20:08:34 +0100 Subject: [PATCH 10/18] Update ScalaAnnotationIntrospectorModule.scala --- .../scala/introspect/ScalaAnnotationIntrospectorModule.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala index 0eced7895..53e7e5f3a 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala @@ -1,7 +1,7 @@ package com.fasterxml.jackson.module.scala.introspect import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.databind.`type`.{ClassKey, CollectionLikeType, CollectionType, ReferenceType, SimpleType} +import com.fasterxml.jackson.databind.`type`.{ClassKey, CollectionLikeType, ReferenceType, SimpleType} import com.fasterxml.jackson.databind.cfg.MapperConfig import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator import com.fasterxml.jackson.databind.deser._ From fc621a28c535aae86a3f24f7f610328b47d3bf88 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 20 Sep 2021 20:14:46 +0100 Subject: [PATCH 11/18] Create OptionWithBooleanDeserializerTest.scala --- .../OptionWithBooleanDeserializerTest.scala | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithBooleanDeserializerTest.scala diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithBooleanDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithBooleanDeserializerTest.scala new file mode 100644 index 000000000..c67659d16 --- /dev/null +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithBooleanDeserializerTest.scala @@ -0,0 +1,57 @@ +package com.fasterxml.jackson.module.scala.deser + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import com.fasterxml.jackson.module.scala.DefaultScalaModule +import com.fasterxml.jackson.module.scala.introspect.ScalaAnnotationIntrospector +import org.scalatest.BeforeAndAfterEach + +object OptionWithBooleanDeserializerTest { + case class AnnotatedOptionBoolean(@JsonDeserialize(contentAs = classOf[java.lang.Boolean]) valueBoolean: Option[Boolean]) + case class AnnotatedOptionPrimitiveBoolean(@JsonDeserialize(contentAs = classOf[Boolean]) valueBoolean: Option[Boolean]) + case class OptionBoolean(valueBoolean: Option[Boolean]) + case class OptionJavaBoolean(valueBoolean: Option[java.lang.Boolean]) +} + +class OptionWithBooleanDeserializerTest extends DeserializerTest with BeforeAndAfterEach { + lazy val module: DefaultScalaModule.type = DefaultScalaModule + import OptionWithBooleanDeserializerTest._ + + private def useOptionBoolean(v: Option[Boolean]): String = v.map(_.toString).getOrElse("null") + private def useOptionJavaBoolean(v: Option[java.lang.Boolean]): String = v.map(_.toString).getOrElse("null") + + override def afterEach(): Unit = { + super.afterEach() + ScalaAnnotationIntrospector.clearRegisteredReferencedTypes() + } + + "JacksonModuleScala" should "support AnnotatedOptionBoolean" in { + val v1 = deserialize("""{"valueBoolean":false}""", classOf[AnnotatedOptionBoolean]) + v1 shouldBe AnnotatedOptionBoolean(Some(false)) + v1.valueBoolean.get shouldBe false + useOptionBoolean(v1.valueBoolean) shouldBe "false" + } + + it should "support AnnotatedOptionPrimitiveBoolean" in { + val v1 = deserialize("""{"valueBoolean":false}""", classOf[AnnotatedOptionPrimitiveBoolean]) + v1 shouldBe AnnotatedOptionPrimitiveBoolean(Some(false)) + v1.valueBoolean.get shouldBe false + useOptionBoolean(v1.valueBoolean) shouldBe "false" + } + + it should "support OptionBoolean" in { + ScalaAnnotationIntrospector.registerReferencedType(classOf[OptionBoolean], "valueBoolean", classOf[Boolean]) + val v1 = deserialize("""{"valueBoolean":false}""", classOf[OptionBoolean]) + v1 shouldBe OptionBoolean(Some(false)) + v1.valueBoolean.get shouldBe false + //this will next call will fail with a Scala unboxing exception unless you ScalaAnnotationIntrospector.registerReferencedType + //or use one of the equivalent classes in OptionWithBooleanDeserializerTest + useOptionBoolean(v1.valueBoolean) shouldBe "false" + } + + it should "support OptionJavaBoolean" in { + val v1 = deserialize("""{"valueBoolean":false}""", classOf[OptionJavaBoolean]) + v1 shouldBe OptionJavaBoolean(Some(false)) + v1.valueBoolean.get shouldBe false + useOptionJavaBoolean(v1.valueBoolean) shouldBe "false" + } +} From 8bd6978ebe872a43d0bf2d8d7e28f9432eeb9164 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 20 Sep 2021 20:16:16 +0100 Subject: [PATCH 12/18] Update OptionWithBooleanDeserializerTest.scala --- .../deser/OptionWithBooleanDeserializerTest.scala | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithBooleanDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithBooleanDeserializerTest.scala index c67659d16..144077086 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithBooleanDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithBooleanDeserializerTest.scala @@ -38,13 +38,18 @@ class OptionWithBooleanDeserializerTest extends DeserializerTest with BeforeAndA useOptionBoolean(v1.valueBoolean) shouldBe "false" } - it should "support OptionBoolean" in { + it should "support OptionBoolean (without registerReferencedType)" in { + val v1 = deserialize("""{"valueBoolean":false}""", classOf[OptionBoolean]) + v1 shouldBe OptionBoolean(Some(false)) + v1.valueBoolean.get shouldBe false + useOptionBoolean(v1.valueBoolean) shouldBe "false" + } + + it should "support OptionBoolean (with registerReferencedType)" in { ScalaAnnotationIntrospector.registerReferencedType(classOf[OptionBoolean], "valueBoolean", classOf[Boolean]) val v1 = deserialize("""{"valueBoolean":false}""", classOf[OptionBoolean]) v1 shouldBe OptionBoolean(Some(false)) v1.valueBoolean.get shouldBe false - //this will next call will fail with a Scala unboxing exception unless you ScalaAnnotationIntrospector.registerReferencedType - //or use one of the equivalent classes in OptionWithBooleanDeserializerTest useOptionBoolean(v1.valueBoolean) shouldBe "false" } From 0445974553e0ed65fc58b481576da8e9245dc79d Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 20 Sep 2021 23:13:09 +0100 Subject: [PATCH 13/18] add map value test --- .../ScalaAnnotationIntrospectorModule.scala | 6 +- .../MapWithNumberValueDeserializerTest.scala | 61 +++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 src/test/scala/com/fasterxml/jackson/module/scala/deser/MapWithNumberValueDeserializerTest.scala diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala index 53e7e5f3a..a86835eef 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala @@ -1,7 +1,7 @@ package com.fasterxml.jackson.module.scala.introspect import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.databind.`type`.{ClassKey, CollectionLikeType, ReferenceType, SimpleType} +import com.fasterxml.jackson.databind.`type`.{ClassKey, CollectionLikeType, MapLikeType, ReferenceType, SimpleType} import com.fasterxml.jackson.databind.cfg.MapperConfig import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator import com.fasterxml.jackson.databind.deser._ @@ -281,10 +281,12 @@ private case class WrappedCreatorProperty(creatorProperty: CreatorProperty, refC extends CreatorProperty(creatorProperty, creatorProperty.getFullName) { override def getType: JavaType = { - super.getType match { + val result = super.getType match { case rt: ReferenceType => ReferenceType.upgradeFrom(rt, SimpleType.constructUnsafe(refClass)) case ct: CollectionLikeType => CollectionLikeType.upgradeFrom(ct, SimpleType.constructUnsafe(refClass)) + case mt: MapLikeType => MapLikeType.upgradeFrom(mt, mt.getKeyType, SimpleType.constructUnsafe(refClass)) case other => other } + result } } diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/MapWithNumberValueDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/MapWithNumberValueDeserializerTest.scala new file mode 100644 index 000000000..d050d4242 --- /dev/null +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/MapWithNumberValueDeserializerTest.scala @@ -0,0 +1,61 @@ +package com.fasterxml.jackson.module.scala.deser + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import com.fasterxml.jackson.module.scala.DefaultScalaModule +import com.fasterxml.jackson.module.scala.introspect.ScalaAnnotationIntrospector +import org.scalatest.BeforeAndAfterEach + +object MapWithNumberValueDeserializerTest { + case class AnnotatedMapLong(@JsonDeserialize(contentAs = classOf[java.lang.Long]) longs: Map[String, Long]) + case class AnnotatedMapPrimitiveLong(@JsonDeserialize(contentAs = classOf[Long]) longs: Map[String, Long]) + case class MapLong(longs: Map[String, Long]) + case class MapJavaLong(longs: Map[String, java.lang.Long]) + case class MapBigInt(longs: Map[String, BigInt]) +} + +class MapWithNumberValueDeserializerTest extends DeserializerTest with BeforeAndAfterEach { + lazy val module: DefaultScalaModule.type = DefaultScalaModule + import MapWithNumberValueDeserializerTest._ + + private def sumMapLong(m: Map[String, Long]): Long = m.values.sum + private def sumMapJavaLong(m: Map[String, java.lang.Long]): Long = m.values.map(_.toLong).sum + private def sumMapBigInt(m: Map[String, BigInt]): Long = m.values.sum.toLong + + override def afterEach(): Unit = { + super.afterEach() + ScalaAnnotationIntrospector.clearRegisteredReferencedTypes() + } + + "JacksonModuleScala" should "support AnnotatedMapLong" in { + val v1 = deserialize("""{"longs":{"151":151,"152":152,"153":153}}""", classOf[AnnotatedMapLong]) + v1 shouldBe AnnotatedMapLong(Map("151" -> 151L, "152" -> 152L, "153" -> 153L)) + sumMapLong(v1.longs) shouldBe 456L + } + + it should "support AnnotatedMapPrimitiveLong" in { + val v1 = deserialize("""{"longs":{"151":151,"152":152,"153":153}}""", classOf[AnnotatedMapPrimitiveLong]) + v1 shouldBe AnnotatedMapPrimitiveLong(Map("151" -> 151L, "152" -> 152L, "153" -> 153L)) + sumMapLong(v1.longs) shouldBe 456L + } + + it should "support MapLong" in { + ScalaAnnotationIntrospector.registerReferencedType(classOf[MapLong], "longs", classOf[Long]) + val v1 = deserialize("""{"longs":{"151":151,"152":152,"153":153}}""", classOf[MapLong]) + v1 shouldBe MapLong(Map("151" -> 151L, "152" -> 152L, "153" -> 153L)) + //this will next call will fail with a Scala unboxing exception unless you ScalaAnnotationIntrospector.registerReferencedType + //or use one of the equivalent classes in MapWithNumberDeserializerTest + sumMapLong(v1.longs) shouldBe 456L + } + + it should "support MapJavaLong" in { + val v1 = deserialize("""{"longs":{"151":151,"152":152,"153":153}}""", classOf[MapJavaLong]) + v1 shouldBe MapJavaLong(Map("151" -> 151L, "152" -> 152L, "153" -> 153L)) + sumMapJavaLong(v1.longs) shouldBe 456L + } + + it should "support MapBigInt" in { + val v1 = deserialize("""{"longs":{"151":151,"152":152,"153":153}}""", classOf[MapBigInt]) + v1 shouldBe MapBigInt(Map("151" -> 151L, "152" -> 152L, "153" -> 153L)) + sumMapBigInt(v1.longs) shouldBe 456L + } +} From 29b75fcd367e75996a8e9f94b24eb30ab50b3087 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 20 Sep 2021 23:14:38 +0100 Subject: [PATCH 14/18] Update ScalaAnnotationIntrospectorModule.scala --- .../scala/introspect/ScalaAnnotationIntrospectorModule.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala index a86835eef..f16b483cb 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala @@ -281,12 +281,11 @@ private case class WrappedCreatorProperty(creatorProperty: CreatorProperty, refC extends CreatorProperty(creatorProperty, creatorProperty.getFullName) { override def getType: JavaType = { - val result = super.getType match { + super.getType match { case rt: ReferenceType => ReferenceType.upgradeFrom(rt, SimpleType.constructUnsafe(refClass)) case ct: CollectionLikeType => CollectionLikeType.upgradeFrom(ct, SimpleType.constructUnsafe(refClass)) case mt: MapLikeType => MapLikeType.upgradeFrom(mt, mt.getKeyType, SimpleType.constructUnsafe(refClass)) case other => other } - result } } From 7e80dbdeadfad21d2c0cdf420fba6a227b12e334 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 20 Sep 2021 23:23:58 +0100 Subject: [PATCH 15/18] Update ScalaAnnotationIntrospectorModule.scala --- .../ScalaAnnotationIntrospectorModule.scala | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala index f16b483cb..fc122b838 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala @@ -14,10 +14,11 @@ import com.fasterxml.jackson.module.scala.util.Implicits._ import java.lang.annotation.Annotation object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueInstantiators { - private [this] var _descriptorCache: LookupCache[ClassKey, BeanDescriptor] = + private[this] var _descriptorCache: LookupCache[ClassKey, BeanDescriptor] = new LRUMap[ClassKey, BeanDescriptor](16, 100) - private case class ClassOverrides(overrides: scala.collection.mutable.Map[String, Class[_]] = scala.collection.mutable.Map.empty) + case class ClassHolder(keyClass: Option[Class[_]] = None, valueClass: Option[Class[_]] = None) + private case class ClassOverrides(overrides: scala.collection.mutable.Map[String, ClassHolder] = scala.collection.mutable.Map.empty) private val overrideMap = scala.collection.mutable.Map[Class[_], ClassOverrides]() @@ -39,7 +40,7 @@ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueI * @since 2.13.0 */ def registerReferencedType(clazz: Class[_], fieldName: String, referencedType: Class[_]): Unit = { - overrideMap.getOrElseUpdate(clazz, ClassOverrides()).overrides.update(fieldName, referencedType) + overrideMap.getOrElseUpdate(clazz, ClassOverrides()).overrides.update(fieldName, ClassHolder(valueClass = Some(referencedType))) } /** @@ -164,7 +165,7 @@ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueI descriptor.properties.find(_.param.exists(_.index == creator.getCreatorIndex)) match { case Some(pd) => { val mappedCreator = overrides.get(pd.name) match { - case Some(refClass) => WrappedCreatorProperty(creator, refClass) + case Some(refHolder) => WrappedCreatorProperty(creator, refHolder) case _ => creator } if (applyDefaultValues) { @@ -277,14 +278,20 @@ trait ScalaAnnotationIntrospectorModule extends JacksonModule { this += { _.addValueInstantiators(ScalaAnnotationIntrospector) } } -private case class WrappedCreatorProperty(creatorProperty: CreatorProperty, refClass: Class[_]) +private case class WrappedCreatorProperty(creatorProperty: CreatorProperty, refHolder: ScalaAnnotationIntrospector.ClassHolder) extends CreatorProperty(creatorProperty, creatorProperty.getFullName) { override def getType: JavaType = { super.getType match { - case rt: ReferenceType => ReferenceType.upgradeFrom(rt, SimpleType.constructUnsafe(refClass)) - case ct: CollectionLikeType => CollectionLikeType.upgradeFrom(ct, SimpleType.constructUnsafe(refClass)) - case mt: MapLikeType => MapLikeType.upgradeFrom(mt, mt.getKeyType, SimpleType.constructUnsafe(refClass)) + case rt: ReferenceType if refHolder.valueClass.isDefined => + ReferenceType.upgradeFrom(rt, SimpleType.constructUnsafe(refHolder.valueClass.get)) + case ct: CollectionLikeType if refHolder.valueClass.isDefined => + CollectionLikeType.upgradeFrom(ct, SimpleType.constructUnsafe(refHolder.valueClass.get)) + case mt: MapLikeType => { + val keyType = refHolder.keyClass.map(SimpleType.constructUnsafe).getOrElse(mt.getKeyType) + val valueType = refHolder.valueClass.map(SimpleType.constructUnsafe).getOrElse(mt.getContentType) + MapLikeType.upgradeFrom(mt, keyType, valueType) + } case other => other } } From 14e21a700680f6c26db0debfa2a289f698b77727 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 20 Sep 2021 23:32:18 +0100 Subject: [PATCH 16/18] fix messages --- .../deser/MapWithNumberValueDeserializerTest.scala | 10 +++++----- .../deser/OptionWithBooleanDeserializerTest.scala | 10 +++++----- .../scala/deser/OptionWithNumberDeserializerTest.scala | 10 +++++----- .../scala/deser/SeqWithNumberDeserializerTest.scala | 10 +++++----- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/MapWithNumberValueDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/MapWithNumberValueDeserializerTest.scala index d050d4242..cf5feebfd 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/MapWithNumberValueDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/MapWithNumberValueDeserializerTest.scala @@ -26,19 +26,19 @@ class MapWithNumberValueDeserializerTest extends DeserializerTest with BeforeAnd ScalaAnnotationIntrospector.clearRegisteredReferencedTypes() } - "JacksonModuleScala" should "support AnnotatedMapLong" in { + "JacksonModuleScala" should "deserialize AnnotatedMapLong" in { val v1 = deserialize("""{"longs":{"151":151,"152":152,"153":153}}""", classOf[AnnotatedMapLong]) v1 shouldBe AnnotatedMapLong(Map("151" -> 151L, "152" -> 152L, "153" -> 153L)) sumMapLong(v1.longs) shouldBe 456L } - it should "support AnnotatedMapPrimitiveLong" in { + it should "deserialize AnnotatedMapPrimitiveLong" in { val v1 = deserialize("""{"longs":{"151":151,"152":152,"153":153}}""", classOf[AnnotatedMapPrimitiveLong]) v1 shouldBe AnnotatedMapPrimitiveLong(Map("151" -> 151L, "152" -> 152L, "153" -> 153L)) sumMapLong(v1.longs) shouldBe 456L } - it should "support MapLong" in { + it should "deserialize MapLong" in { ScalaAnnotationIntrospector.registerReferencedType(classOf[MapLong], "longs", classOf[Long]) val v1 = deserialize("""{"longs":{"151":151,"152":152,"153":153}}""", classOf[MapLong]) v1 shouldBe MapLong(Map("151" -> 151L, "152" -> 152L, "153" -> 153L)) @@ -47,13 +47,13 @@ class MapWithNumberValueDeserializerTest extends DeserializerTest with BeforeAnd sumMapLong(v1.longs) shouldBe 456L } - it should "support MapJavaLong" in { + it should "deserialize MapJavaLong" in { val v1 = deserialize("""{"longs":{"151":151,"152":152,"153":153}}""", classOf[MapJavaLong]) v1 shouldBe MapJavaLong(Map("151" -> 151L, "152" -> 152L, "153" -> 153L)) sumMapJavaLong(v1.longs) shouldBe 456L } - it should "support MapBigInt" in { + it should "deserialize MapBigInt" in { val v1 = deserialize("""{"longs":{"151":151,"152":152,"153":153}}""", classOf[MapBigInt]) v1 shouldBe MapBigInt(Map("151" -> 151L, "152" -> 152L, "153" -> 153L)) sumMapBigInt(v1.longs) shouldBe 456L diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithBooleanDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithBooleanDeserializerTest.scala index 144077086..1caedf42a 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithBooleanDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithBooleanDeserializerTest.scala @@ -24,28 +24,28 @@ class OptionWithBooleanDeserializerTest extends DeserializerTest with BeforeAndA ScalaAnnotationIntrospector.clearRegisteredReferencedTypes() } - "JacksonModuleScala" should "support AnnotatedOptionBoolean" in { + "JacksonModuleScala" should "deserialize AnnotatedOptionBoolean" in { val v1 = deserialize("""{"valueBoolean":false}""", classOf[AnnotatedOptionBoolean]) v1 shouldBe AnnotatedOptionBoolean(Some(false)) v1.valueBoolean.get shouldBe false useOptionBoolean(v1.valueBoolean) shouldBe "false" } - it should "support AnnotatedOptionPrimitiveBoolean" in { + it should "deserialize AnnotatedOptionPrimitiveBoolean" in { val v1 = deserialize("""{"valueBoolean":false}""", classOf[AnnotatedOptionPrimitiveBoolean]) v1 shouldBe AnnotatedOptionPrimitiveBoolean(Some(false)) v1.valueBoolean.get shouldBe false useOptionBoolean(v1.valueBoolean) shouldBe "false" } - it should "support OptionBoolean (without registerReferencedType)" in { + it should "deserialize OptionBoolean (without registerReferencedType)" in { val v1 = deserialize("""{"valueBoolean":false}""", classOf[OptionBoolean]) v1 shouldBe OptionBoolean(Some(false)) v1.valueBoolean.get shouldBe false useOptionBoolean(v1.valueBoolean) shouldBe "false" } - it should "support OptionBoolean (with registerReferencedType)" in { + it should "deserialize OptionBoolean (with registerReferencedType)" in { ScalaAnnotationIntrospector.registerReferencedType(classOf[OptionBoolean], "valueBoolean", classOf[Boolean]) val v1 = deserialize("""{"valueBoolean":false}""", classOf[OptionBoolean]) v1 shouldBe OptionBoolean(Some(false)) @@ -53,7 +53,7 @@ class OptionWithBooleanDeserializerTest extends DeserializerTest with BeforeAndA useOptionBoolean(v1.valueBoolean) shouldBe "false" } - it should "support OptionJavaBoolean" in { + it should "deserialize OptionJavaBoolean" in { val v1 = deserialize("""{"valueBoolean":false}""", classOf[OptionJavaBoolean]) v1 shouldBe OptionJavaBoolean(Some(false)) v1.valueBoolean.get shouldBe false diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala index 6e042c972..ed9ebb35f 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala @@ -26,21 +26,21 @@ class OptionWithNumberDeserializerTest extends DeserializerTest with BeforeAndAf ScalaAnnotationIntrospector.clearRegisteredReferencedTypes() } - "JacksonModuleScala" should "support AnnotatedOptionLong" in { + "JacksonModuleScala" should "deserialize AnnotatedOptionLong" in { val v1 = deserialize("""{"valueLong":151}""", classOf[AnnotatedOptionLong]) v1 shouldBe AnnotatedOptionLong(Some(151L)) v1.valueLong.get shouldBe 151L useOptionLong(v1.valueLong) shouldBe 302L } - it should "support AnnotatedOptionPrimitiveLong" in { + it should "deserialize AnnotatedOptionPrimitiveLong" in { val v1 = deserialize("""{"valueLong":151}""", classOf[AnnotatedOptionPrimitiveLong]) v1 shouldBe AnnotatedOptionPrimitiveLong(Some(151L)) v1.valueLong.get shouldBe 151L useOptionLong(v1.valueLong) shouldBe 302L } - it should "support OptionLong" in { + it should "deserialize OptionLong" in { ScalaAnnotationIntrospector.registerReferencedType(classOf[OptionLong], "valueLong", classOf[Long]) val v1 = deserialize("""{"valueLong":151}""", classOf[OptionLong]) v1 shouldBe OptionLong(Some(151L)) @@ -50,14 +50,14 @@ class OptionWithNumberDeserializerTest extends DeserializerTest with BeforeAndAf useOptionLong(v1.valueLong) shouldBe 302L } - it should "support OptionJavaLong" in { + it should "deserialize OptionJavaLong" in { val v1 = deserialize("""{"valueLong":151}""", classOf[OptionJavaLong]) v1 shouldBe OptionJavaLong(Some(151L)) v1.valueLong.get shouldBe 151L useOptionJavaLong(v1.valueLong) shouldBe 302L } - it should "support OptionBigInt" in { + it should "deserialize OptionBigInt" in { val v1 = deserialize("""{"value":151}""", classOf[OptionBigInt]) v1 shouldBe OptionBigInt(Some(BigInt(151L))) v1.value.get shouldBe 151L diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/SeqWithNumberDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/SeqWithNumberDeserializerTest.scala index ad7f15819..2b9e4c6fb 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/SeqWithNumberDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/SeqWithNumberDeserializerTest.scala @@ -26,19 +26,19 @@ class SeqWithNumberDeserializerTest extends DeserializerTest with BeforeAndAfter ScalaAnnotationIntrospector.clearRegisteredReferencedTypes() } - "JacksonModuleScala" should "support AnnotatedSeqLong" in { + "JacksonModuleScala" should "deserialize AnnotatedSeqLong" in { val v1 = deserialize("""{"longs":[151,152,153]}""", classOf[AnnotatedSeqLong]) v1 shouldBe AnnotatedSeqLong(Seq(151L, 152L, 153L)) sumSeqLong(v1.longs) shouldBe 456L } - it should "support AnnotatedSeqPrimitiveLong" in { + it should "deserialize AnnotatedSeqPrimitiveLong" in { val v1 = deserialize("""{"longs":[151,152,153]}""", classOf[AnnotatedSeqPrimitiveLong]) v1 shouldBe AnnotatedSeqPrimitiveLong(Seq(151L, 152L, 153L)) sumSeqLong(v1.longs) shouldBe 456L } - it should "support SeqLong" in { + it should "deserialize SeqLong" in { ScalaAnnotationIntrospector.registerReferencedType(classOf[SeqLong], "longs", classOf[Long]) val v1 = deserialize("""{"longs":[151,152,153]}""", classOf[SeqLong]) v1 shouldBe SeqLong(Seq(151L, 152L, 153L)) @@ -47,13 +47,13 @@ class SeqWithNumberDeserializerTest extends DeserializerTest with BeforeAndAfter sumSeqLong(v1.longs) shouldBe 456L } - it should "support SeqJavaLong" in { + it should "deserialize SeqJavaLong" in { val v1 = deserialize("""{"longs":[151,152,153]}""", classOf[SeqJavaLong]) v1 shouldBe SeqJavaLong(Seq(151L, 152L, 153L)) sumSeqJavaLong(v1.longs) shouldBe 456L } - it should "support SeqBigInt" in { + it should "deserialize SeqBigInt" in { val v1 = deserialize("""{"longs":[151,152,153]}""", classOf[SeqBigInt]) v1 shouldBe SeqBigInt(Seq(151L, 152L, 153L)) sumSeqBigInt(v1.longs) shouldBe 456L From 3b1bca12c6f2761862769c22d94d340a3799dd4d Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 20 Sep 2021 23:51:36 +0100 Subject: [PATCH 17/18] rework --- build.sbt | 2 + .../ScalaAnnotationIntrospectorModule.scala | 41 +++++++++++++++++-- .../MapWithNumberValueDeserializerTest.scala | 4 +- .../OptionWithBooleanDeserializerTest.scala | 6 +-- .../OptionWithNumberDeserializerTest.scala | 4 +- .../deser/SeqWithNumberDeserializerTest.scala | 4 +- 6 files changed, 48 insertions(+), 13 deletions(-) diff --git a/build.sbt b/build.sbt index 24db83ae7..f2e4282c4 100644 --- a/build.sbt +++ b/build.sbt @@ -103,6 +103,8 @@ Compile / resourceGenerators += Def.task { Seq(file) }.taskValue +Test / parallelExecution := false + ThisBuild / githubWorkflowJavaVersions := Seq("adopt@1.8", "adopt@1.11") ThisBuild / githubWorkflowBuild := Seq(WorkflowStep.Sbt(List("test", "mimaReportBinaryIssues"))) ThisBuild / githubWorkflowTargetTags ++= Seq("v*") diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala index fc122b838..86dd5e57f 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala @@ -35,18 +35,50 @@ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueI * @param clazz the (case) class * @param fieldName the field name in the (case) class * @param referencedType the referenced type of the field - for `Option[Long]` - the referenced type is `Long` + * @see [[registerReferencedKeyType]] * @see [[clearRegisteredReferencedTypes()]] * @see [[clearRegisteredReferencedTypes(Class[_])]] * @since 2.13.0 */ - def registerReferencedType(clazz: Class[_], fieldName: String, referencedType: Class[_]): Unit = { - overrideMap.getOrElseUpdate(clazz, ClassOverrides()).overrides.update(fieldName, ClassHolder(valueClass = Some(referencedType))) + def registerReferencedValueType(clazz: Class[_], fieldName: String, referencedType: Class[_]): Unit = { + val overrides = overrideMap.getOrElseUpdate(clazz, ClassOverrides()).overrides + overrides.get(fieldName) match { + case Some(holder) => overrides.put(fieldName, holder.copy(valueClass = Some(referencedType))) + case _ => overrides.put(fieldName, ClassHolder(valueClass = Some(referencedType))) + } + } + + /** + * jackson-module-scala does not always properly handle deserialization of Options or Collections wrapping + * Scala primitives (eg Int, Long, Boolean). There are general issues with serializing and deserializing + * Scala 2 Enumerations. This function will not help with Enumerations. + *

+ * This function is experimental and may be removed or significantly reworked in a later release. + *

+ * These issues can be worked around by adding Jackson annotations on the affected fields. + * This function is designed to be used when it is not possible to apply Jackson annotations. + * + * @param clazz the (case) class + * @param fieldName the field name in the (case) class + * @param referencedType the referenced type of the key field - for `Map[Long, String]` - the referenced key type is `Long` + * @see [[registerReferencedValueType]] + * @see [[clearRegisteredReferencedTypes()]] + * @see [[clearRegisteredReferencedTypes(Class[_])]] + * @since 2.13.0 + */ + def registerReferencedKeyType(clazz: Class[_], fieldName: String, referencedType: Class[_]): Unit = { + val overrides = overrideMap.getOrElseUpdate(clazz, ClassOverrides()).overrides + overrides.get(fieldName) match { + case Some(holder) => overrides.put(fieldName, holder.copy(keyClass = Some(referencedType))) + case _ => overrides.put(fieldName, ClassHolder(keyClass = Some(referencedType))) + } } /** * clears the state associated with reference types for the given class + * * @param clazz the class for which to remove the registered reference types - * @see [[registerReferencedType]] + * @see [[registerReferencedValueType]] * @see [[clearRegisteredReferencedTypes()]] * @since 2.13.0 */ @@ -56,7 +88,8 @@ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueI /** * clears all the state associated with reference types - * @see [[registerReferencedType]] + * + * @see [[registerReferencedValueType]] * @see [[clearRegisteredReferencedTypes(Class[_])]] * @since 2.13.0 */ diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/MapWithNumberValueDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/MapWithNumberValueDeserializerTest.scala index cf5feebfd..c7fa3dff5 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/MapWithNumberValueDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/MapWithNumberValueDeserializerTest.scala @@ -39,10 +39,10 @@ class MapWithNumberValueDeserializerTest extends DeserializerTest with BeforeAnd } it should "deserialize MapLong" in { - ScalaAnnotationIntrospector.registerReferencedType(classOf[MapLong], "longs", classOf[Long]) + ScalaAnnotationIntrospector.registerReferencedValueType(classOf[MapLong], "longs", classOf[Long]) val v1 = deserialize("""{"longs":{"151":151,"152":152,"153":153}}""", classOf[MapLong]) v1 shouldBe MapLong(Map("151" -> 151L, "152" -> 152L, "153" -> 153L)) - //this will next call will fail with a Scala unboxing exception unless you ScalaAnnotationIntrospector.registerReferencedType + //this will next call will fail with a Scala unboxing exception unless you ScalaAnnotationIntrospector.registerReferencedValueType //or use one of the equivalent classes in MapWithNumberDeserializerTest sumMapLong(v1.longs) shouldBe 456L } diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithBooleanDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithBooleanDeserializerTest.scala index 1caedf42a..b4c8c24bb 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithBooleanDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithBooleanDeserializerTest.scala @@ -38,15 +38,15 @@ class OptionWithBooleanDeserializerTest extends DeserializerTest with BeforeAndA useOptionBoolean(v1.valueBoolean) shouldBe "false" } - it should "deserialize OptionBoolean (without registerReferencedType)" in { + it should "deserialize OptionBoolean (without registerReferencedValueType)" in { val v1 = deserialize("""{"valueBoolean":false}""", classOf[OptionBoolean]) v1 shouldBe OptionBoolean(Some(false)) v1.valueBoolean.get shouldBe false useOptionBoolean(v1.valueBoolean) shouldBe "false" } - it should "deserialize OptionBoolean (with registerReferencedType)" in { - ScalaAnnotationIntrospector.registerReferencedType(classOf[OptionBoolean], "valueBoolean", classOf[Boolean]) + it should "deserialize OptionBoolean (with registerReferencedValueType)" in { + ScalaAnnotationIntrospector.registerReferencedValueType(classOf[OptionBoolean], "valueBoolean", classOf[Boolean]) val v1 = deserialize("""{"valueBoolean":false}""", classOf[OptionBoolean]) v1 shouldBe OptionBoolean(Some(false)) v1.valueBoolean.get shouldBe false diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala index ed9ebb35f..ac582ade2 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithNumberDeserializerTest.scala @@ -41,11 +41,11 @@ class OptionWithNumberDeserializerTest extends DeserializerTest with BeforeAndAf } it should "deserialize OptionLong" in { - ScalaAnnotationIntrospector.registerReferencedType(classOf[OptionLong], "valueLong", classOf[Long]) + ScalaAnnotationIntrospector.registerReferencedValueType(classOf[OptionLong], "valueLong", classOf[Long]) val v1 = deserialize("""{"valueLong":151}""", classOf[OptionLong]) v1 shouldBe OptionLong(Some(151L)) v1.valueLong.get shouldBe 151L - //this will next call will fail with a Scala unboxing exception unless you ScalaAnnotationIntrospector.registerReferencedType + //this will next call will fail with a Scala unboxing exception unless you ScalaAnnotationIntrospector.registerReferencedValueType //or use one of the equivalent classes in OptionWithNumberDeserializerTest useOptionLong(v1.valueLong) shouldBe 302L } diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/SeqWithNumberDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/SeqWithNumberDeserializerTest.scala index 2b9e4c6fb..f2f4d2256 100644 --- a/src/test/scala/com/fasterxml/jackson/module/scala/deser/SeqWithNumberDeserializerTest.scala +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/SeqWithNumberDeserializerTest.scala @@ -39,10 +39,10 @@ class SeqWithNumberDeserializerTest extends DeserializerTest with BeforeAndAfter } it should "deserialize SeqLong" in { - ScalaAnnotationIntrospector.registerReferencedType(classOf[SeqLong], "longs", classOf[Long]) + ScalaAnnotationIntrospector.registerReferencedValueType(classOf[SeqLong], "longs", classOf[Long]) val v1 = deserialize("""{"longs":[151,152,153]}""", classOf[SeqLong]) v1 shouldBe SeqLong(Seq(151L, 152L, 153L)) - //this will next call will fail with a Scala unboxing exception unless you ScalaAnnotationIntrospector.registerReferencedType + //this will next call will fail with a Scala unboxing exception unless you ScalaAnnotationIntrospector.registerReferencedValueType //or use one of the equivalent classes in SeqWithNumberDeserializerTest sumSeqLong(v1.longs) shouldBe 456L } From 0ed788e531cd288503efe35119dd68ff2aada465 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 21 Sep 2021 14:36:05 +0100 Subject: [PATCH 18/18] Update ScalaAnnotationIntrospectorModule.scala --- .../ScalaAnnotationIntrospectorModule.scala | 34 ++----------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala index 86dd5e57f..8f5b1070f 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/ScalaAnnotationIntrospectorModule.scala @@ -17,7 +17,7 @@ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueI private[this] var _descriptorCache: LookupCache[ClassKey, BeanDescriptor] = new LRUMap[ClassKey, BeanDescriptor](16, 100) - case class ClassHolder(keyClass: Option[Class[_]] = None, valueClass: Option[Class[_]] = None) + case class ClassHolder(valueClass: Option[Class[_]] = None) private case class ClassOverrides(overrides: scala.collection.mutable.Map[String, ClassHolder] = scala.collection.mutable.Map.empty) private val overrideMap = scala.collection.mutable.Map[Class[_], ClassOverrides]() @@ -35,7 +35,6 @@ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueI * @param clazz the (case) class * @param fieldName the field name in the (case) class * @param referencedType the referenced type of the field - for `Option[Long]` - the referenced type is `Long` - * @see [[registerReferencedKeyType]] * @see [[clearRegisteredReferencedTypes()]] * @see [[clearRegisteredReferencedTypes(Class[_])]] * @since 2.13.0 @@ -48,32 +47,6 @@ object ScalaAnnotationIntrospector extends NopAnnotationIntrospector with ValueI } } - /** - * jackson-module-scala does not always properly handle deserialization of Options or Collections wrapping - * Scala primitives (eg Int, Long, Boolean). There are general issues with serializing and deserializing - * Scala 2 Enumerations. This function will not help with Enumerations. - *

- * This function is experimental and may be removed or significantly reworked in a later release. - *

- * These issues can be worked around by adding Jackson annotations on the affected fields. - * This function is designed to be used when it is not possible to apply Jackson annotations. - * - * @param clazz the (case) class - * @param fieldName the field name in the (case) class - * @param referencedType the referenced type of the key field - for `Map[Long, String]` - the referenced key type is `Long` - * @see [[registerReferencedValueType]] - * @see [[clearRegisteredReferencedTypes()]] - * @see [[clearRegisteredReferencedTypes(Class[_])]] - * @since 2.13.0 - */ - def registerReferencedKeyType(clazz: Class[_], fieldName: String, referencedType: Class[_]): Unit = { - val overrides = overrideMap.getOrElseUpdate(clazz, ClassOverrides()).overrides - overrides.get(fieldName) match { - case Some(holder) => overrides.put(fieldName, holder.copy(keyClass = Some(referencedType))) - case _ => overrides.put(fieldName, ClassHolder(keyClass = Some(referencedType))) - } - } - /** * clears the state associated with reference types for the given class * @@ -314,16 +287,15 @@ trait ScalaAnnotationIntrospectorModule extends JacksonModule { private case class WrappedCreatorProperty(creatorProperty: CreatorProperty, refHolder: ScalaAnnotationIntrospector.ClassHolder) extends CreatorProperty(creatorProperty, creatorProperty.getFullName) { - override def getType: JavaType = { + override def getType(): JavaType = { super.getType match { case rt: ReferenceType if refHolder.valueClass.isDefined => ReferenceType.upgradeFrom(rt, SimpleType.constructUnsafe(refHolder.valueClass.get)) case ct: CollectionLikeType if refHolder.valueClass.isDefined => CollectionLikeType.upgradeFrom(ct, SimpleType.constructUnsafe(refHolder.valueClass.get)) case mt: MapLikeType => { - val keyType = refHolder.keyClass.map(SimpleType.constructUnsafe).getOrElse(mt.getKeyType) val valueType = refHolder.valueClass.map(SimpleType.constructUnsafe).getOrElse(mt.getContentType) - MapLikeType.upgradeFrom(mt, keyType, valueType) + MapLikeType.upgradeFrom(mt, mt.getKeyType, valueType) } case other => other }