From c622577c803c66004a566124b79821d2c7f15224 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 4 Dec 2023 18:15:18 +1000 Subject: [PATCH] Fix regression in Map deser, in Scala 2.13 w default typing (#658) 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 | 13 ++++++- .../DefaultTypingMapDeserializerTest.scala | 35 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) 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..697985c8 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 @@ -14,13 +14,24 @@ 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 java.util.AbstractMap[A, B] { + private val wrapped = delegate.asJava + + override def entrySet(): java.util.Set[java.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) } 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])