Skip to content

Commit

Permalink
Fix regression in Map deser, in Scala 2.13 w default typing (#658)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
retronym authored and pjfanning committed Dec 4, 2023
1 parent e5d0db1 commit c622577
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}


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 c622577

Please sign in to comment.