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/BeanIntrospector.scala b/src/main/scala/com/fasterxml/jackson/module/scala/introspect/BeanIntrospector.scala index 94faf3c54..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,11 +37,6 @@ import scala.reflect.NameTransformer object BeanIntrospector { - private def getCtorParams(ctor: Constructor[_]): Seq[String] = { - val names = JavaParameterIntrospector.getCtorParamNames(ctor) - names.map(NameTransformer.decode) - } - def apply[T <: AnyRef](cls: Class[_]) = { /** @@ -246,4 +241,9 @@ object BeanIntrospector { BeanDescriptor(cls, fields ++ methods ++ lazyValMethods) } + + 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..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 @@ -1,21 +1,75 @@ 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.`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._ 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, JavaType, MapperFeature} import com.fasterxml.jackson.module.scala.JacksonModule 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) + 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]() + + /** + * 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 field - for `Option[Long]` - the referenced type is `Long` + * @see [[clearRegisteredReferencedTypes()]] + * @see [[clearRegisteredReferencedTypes(Class[_])]] + * @since 2.13.0 + */ + 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))) + } + } + + /** + * clears the state associated with reference types for the given class + * + * @param clazz the class for which to remove the registered reference types + * @see [[registerReferencedValueType]] + * @see [[clearRegisteredReferencedTypes()]] + * @since 2.13.0 + */ + def clearRegisteredReferencedTypes(clazz: Class[_]): Unit = { + overrideMap.remove(clazz) + } + + /** + * clears all the state associated with reference types + * + * @see [[registerReferencedValueType]] + * @see [[clearRegisteredReferencedTypes(Class[_])]] + * @since 2.13.0 + */ + def clearRegisteredReferencedTypes(): Unit = { + overrideMap.clear() + } + def setDescriptorCache(cache: LookupCache[ClassKey, BeanDescriptor]): LookupCache[ClassKey, BeanDescriptor] = { val existingCache = _descriptorCache _descriptorCache = cache @@ -106,22 +160,38 @@ 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 => { + case Some(array) if (applyDefaultValues || overrides.nonEmpty) => { array.map { - case creator: CreatorProperty => + 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 Some(pd) => { + val mappedCreator = overrides.get(pd.name) match { + case Some(refHolder) => WrappedCreatorProperty(creator, refHolder) + case _ => creator + } + if (applyDefaultValues) { + pd match { + case PropertyDescriptor(_, Some(ConstructorParameter(_, _, Some(defaultValue))), _, _, _, _, _) => { + mappedCreator.withNullProvider(new NullValueProvider { + override def getNullValue(ctxt: DeserializationContext): AnyRef = defaultValue() + + override def getNullAccessPattern: AccessPattern = AccessPattern.DYNAMIC + }) + } + case _ => mappedCreator + } + } else { + mappedCreator + } + } case _ => creator } - case other => other + } } } case Some(array) => array @@ -140,7 +210,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) @@ -213,3 +283,21 @@ trait ScalaAnnotationIntrospectorModule extends JacksonModule { this += { _.appendAnnotationIntrospector(ScalaAnnotationIntrospector) } this += { _.addValueInstantiators(ScalaAnnotationIntrospector) } } + +private case class WrappedCreatorProperty(creatorProperty: CreatorProperty, refHolder: ScalaAnnotationIntrospector.ClassHolder) + extends CreatorProperty(creatorProperty, creatorProperty.getFullName) { + + 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 valueType = refHolder.valueClass.map(SimpleType.constructUnsafe).getOrElse(mt.getContentType) + MapLikeType.upgradeFrom(mt, mt.getKeyType, valueType) + } + case other => other + } + } +} 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..c7fa3dff5 --- /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 "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 "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 "deserialize MapLong" in { + 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.registerReferencedValueType + //or use one of the equivalent classes in MapWithNumberDeserializerTest + sumMapLong(v1.longs) shouldBe 456L + } + + 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 "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 new file mode 100644 index 000000000..b4c8c24bb --- /dev/null +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/OptionWithBooleanDeserializerTest.scala @@ -0,0 +1,62 @@ +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 "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 "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 "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 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 + useOptionBoolean(v1.valueBoolean) shouldBe "false" + } + + it should "deserialize OptionJavaBoolean" in { + val v1 = deserialize("""{"valueBoolean":false}""", classOf[OptionJavaBoolean]) + v1 shouldBe OptionJavaBoolean(Some(false)) + v1.valueBoolean.get shouldBe false + useOptionJavaBoolean(v1.valueBoolean) 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 0192a1eb7..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 @@ -2,6 +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.ScalaAnnotationIntrospector +import org.scalatest.BeforeAndAfterEach object OptionWithNumberDeserializerTest { case class AnnotatedOptionLong(@JsonDeserialize(contentAs = classOf[java.lang.Long]) valueLong: Option[Long]) @@ -11,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._ @@ -19,37 +21,43 @@ 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) - "JacksonModuleScala" should "support AnnotatedOptionLong" in { + override def afterEach(): Unit = { + super.afterEach() + ScalaAnnotationIntrospector.clearRegisteredReferencedTypes() + } + + "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.registerReferencedValueType(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 ScalaAnnotationIntrospector.registerReferencedValueType + //or use one of the equivalent classes in OptionWithNumberDeserializerTest + 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 new file mode 100644 index 000000000..f2f4d2256 --- /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 "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 "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 "deserialize SeqLong" in { + 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.registerReferencedValueType + //or use one of the equivalent classes in SeqWithNumberDeserializerTest + sumSeqLong(v1.longs) shouldBe 456L + } + + 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 "deserialize SeqBigInt" in { + val v1 = deserialize("""{"longs":[151,152,153]}""", classOf[SeqBigInt]) + v1 shouldBe SeqBigInt(Seq(151L, 152L, 153L)) + sumSeqBigInt(v1.longs) shouldBe 456L + } +}