From 981db7fa04d3a2bf7cd1f5734b8d99907ab78118 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 4 Dec 2023 13:58:28 +1000 Subject: [PATCH] Fix regression in Map deserializtion in Scala 2.13 when default typing is enabled. This Map serializer is implemented in terms of the Java version and is somewhat fragile, as discussed in #643 / #470. This commit ensures that the wrapper class is a member class in both Scala and Java. That results in this JSON as the serializer code chooses not to put the inner class name in the type annotation. ``` {"m":["scala.collection.immutable.Map",{"one":"one","two":"two"}]} ``` Fixes #643 --- .../scala/ser/MapSerializerModule.scala | 17 +++++++-- .../DefaultTypingMapDeserializerTest.scala | 35 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 src/test/scala/com/fasterxml/jackson/module/scala/deser/DefaultTypingMapDeserializerTest.scala diff --git a/src/main/scala/com/fasterxml/jackson/module/scala/ser/MapSerializerModule.scala b/src/main/scala/com/fasterxml/jackson/module/scala/ser/MapSerializerModule.scala index 773076ea..a81cd363 100644 --- a/src/main/scala/com/fasterxml/jackson/module/scala/ser/MapSerializerModule.scala +++ b/src/main/scala/com/fasterxml/jackson/module/scala/ser/MapSerializerModule.scala @@ -8,20 +8,33 @@ import com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer import com.fasterxml.jackson.databind.util.StdConverter import com.fasterxml.jackson.module.scala.modifiers.MapTypeModifierModule +import java.util import scala.collection.JavaConverters._ import scala.collection.Map private class MapConverter(inputType: JavaType, config: SerializationConfig) extends StdConverter[Map[_,_],java.util.Map[_,_]] { + // Making this an inner class avoids deserializaion errors when polymorphic typing + // is enabled. In Scala 2.12 `delegate.asJava` happened to be an inner class but + // this implementation detail changed in 2.13. + // + // Tested in DefaultTypingMapDeserializerTest + private class MapWrapper[A, B](delegate: Map[A, B]) extends util.AbstractMap[A, B] { + private val wrapped = delegate.asJava + + override def entrySet(): util.Set[util.Map.Entry[A, B]] = wrapped.entrySet() + } + def convert(value: Map[_,_]): java.util.Map[_,_] = { val m = if (config.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES)) { value } else { value.filter(_._2 != None) } - m.asJava - } + new MapWrapper(m) +// m.asJava + }`` override def getInputType(factory: TypeFactory) = inputType diff --git a/src/test/scala/com/fasterxml/jackson/module/scala/deser/DefaultTypingMapDeserializerTest.scala b/src/test/scala/com/fasterxml/jackson/module/scala/deser/DefaultTypingMapDeserializerTest.scala new file mode 100644 index 00000000..057ffe35 --- /dev/null +++ b/src/test/scala/com/fasterxml/jackson/module/scala/deser/DefaultTypingMapDeserializerTest.scala @@ -0,0 +1,35 @@ +package com.fasterxml.jackson.module.scala.deser + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.scala.DefaultScalaModule + +import scala.collection.immutable + +class DefaultTypingMapDeserializerTest extends DeserializerTest { + + def module: DefaultScalaModule.type = DefaultScalaModule + + override def newMapper: ObjectMapper = { + val mapper = super.newMapper + mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator) + } + + "Scala Module" should "deserialize immutable Map when default typing enabled" in { + val map = HasMap(immutable.Map("one" -> "one", "two" -> "two")) + + val mapper = newMapper + + val json = mapper.writeValueAsString(map) + // Was failing in Scala 2.13+ with: + // > Could not resolve type id 'scala.collection.convert.JavaCollectionWrappers$MapWrapper' as a subtype of + // > `scala.collection.immutable.Map`: Not a subtype + // + // prior the changing MapSerializerModule.scala to use an inner class for MapWrapper + val read = mapper.readValue(json, classOf[HasMap]) + + read shouldEqual map + } + +} + +case class HasMap(m: Map[String, String])