Skip to content

Commit

Permalink
Fix regression in Map deserializtion in Scala 2.13 when default typin…
Browse files Browse the repository at this point in the history
…g is enabled.

This Map serializer is implemented in terms of the Java version and is
somewhat fragile, as discussed in FasterXML#643 / FasterXML#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 FasterXML#643
  • Loading branch information
retronym committed Dec 4, 2023
1 parent 020e558 commit f2ed177
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ object JacksonModule {

props.asScala
}
lazy val version: Version = {
lazy val version: Version = try {
val groupId = buildProps("groupId")
val artifactId = buildProps("artifactId")
val version = buildProps("version")
VersionUtil.parseVersion(version, groupId, artifactId)
} catch {
case _: NoSuchFieldException =>
Version.unknownVersion()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<java.lang.String,java.lang.String>`: 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])

0 comments on commit f2ed177

Please sign in to comment.