Skip to content

Commit 5bc4432

Browse files
authored
Add merge support for Scala collections (#481)
Thanks. Merged.
1 parent 5d6736c commit 5bc4432

File tree

4 files changed

+147
-6
lines changed

4 files changed

+147
-6
lines changed

src/main/scala/com/fasterxml/jackson/module/scala/deser/GenericFactoryDeserializerResolver.scala

+15-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ abstract class GenericFactoryDeserializerResolver[CC[_], CF[X[_]]] extends Deser
4848

4949
// Required by AbstractCollection, but not implemented
5050
override def iterator(): util.Iterator[A] = None.orNull
51+
52+
def setInitialValue(init: Collection[_]): Unit = init.asInstanceOf[Iterable[A]].foreach(add)
5153
}
5254

5355
private class Instantiator(config: DeserializationConfig, collectionType: JavaType, valueType: JavaType)
@@ -82,9 +84,21 @@ abstract class GenericFactoryDeserializerResolver[CC[_], CF[X[_]]] extends Deser
8284
}
8385
}
8486

87+
override def deserialize(jp: JsonParser, ctxt: DeserializationContext, intoValue: CC[_]): CC[_] = {
88+
val bw = newBuilderWrapper(ctxt)
89+
bw.setInitialValue(intoValue)
90+
containerDeserializer.deserialize(jp, ctxt, bw) match {
91+
case wrapper: BuilderWrapper[_] => wrapper.builder.result()
92+
}
93+
}
94+
8595
override def getEmptyValue(ctxt: DeserializationContext): Object = {
86-
val bw = containerDeserializer.getValueInstantiator.createUsingDefault(ctxt).asInstanceOf[BuilderWrapper[AnyRef]]
96+
val bw = newBuilderWrapper(ctxt)
8797
bw.builder.result().asInstanceOf[Object]
8898
}
99+
100+
private def newBuilderWrapper(ctxt: DeserializationContext): BuilderWrapper[AnyRef] = {
101+
containerDeserializer.getValueInstantiator.createUsingDefault(ctxt).asInstanceOf[BuilderWrapper[AnyRef]]
102+
}
89103
}
90104
}

src/main/scala/com/fasterxml/jackson/module/scala/deser/GenericMapFactoryDeserializerResolver.scala

+28-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import com.fasterxml.jackson.databind.deser.{ContextualDeserializer, Deserialize
77
import com.fasterxml.jackson.databind.deser.std.{ContainerDeserializerBase, MapDeserializer, StdValueInstantiator}
88
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer
99

10-
import scala.collection.mutable
10+
import scala.collection.{Map, mutable}
1111
import scala.language.higherKinds
1212

1313
abstract class GenericMapFactoryDeserializerResolver[CC[K, V], CF[X[_, _]]] extends Deserializers.Base {
@@ -39,15 +39,26 @@ abstract class GenericMapFactoryDeserializerResolver[CC[K, V], CF[X[_, _]]] exte
3939
}
4040
}
4141

42-
private class BuilderWrapper[K,V](val builder: Builder[K, V]) extends java.util.AbstractMap[K, V] {
42+
private class BuilderWrapper[K, V >: AnyRef](val builder: Builder[K, V]) extends java.util.AbstractMap[K, V] {
43+
private var baseMap: Map[Any, V] = Map.empty
44+
4345
override def put(k: K, v: V): V = { builder += ((k, v)); v }
4446

47+
// Used by the deserializer when using readerForUpdating
48+
override def get(key: Any): V = baseMap.get(key).orNull
49+
4550
// Isn't used by the deserializer
46-
def entrySet(): java.util.Set[java.util.Map.Entry[K, V]] = throw new UnsupportedOperationException
51+
override def entrySet(): java.util.Set[java.util.Map.Entry[K, V]] = throw new UnsupportedOperationException
52+
53+
def setInitialValue(init: Collection[_, _]): Unit = {
54+
init.asInstanceOf[Map[K, V]].foreach(Function.tupled(put))
55+
baseMap = init.asInstanceOf[Map[Any, V]]
56+
}
4757
}
4858

4959
private class Instantiator(config: DeserializationConfig, mapType: MapLikeType) extends StdValueInstantiator(config, mapType) {
5060
override def canCreateUsingDefault = true
61+
5162
override def createUsingDefault(ctxt: DeserializationContext) =
5263
new BuilderWrapper[AnyRef, AnyRef](builderFor[AnyRef, AnyRef](mapType.getRawClass, mapType.getKeyType, mapType.getContentType))
5364
}
@@ -60,6 +71,7 @@ abstract class GenericMapFactoryDeserializerResolver[CC[K, V], CF[X[_, _]]] exte
6071
}
6172

6273
override def getContentType: JavaType = containerDeserializer.getContentType
74+
6375
override def getContentDeserializer: JsonDeserializer[AnyRef] = containerDeserializer.getContentDeserializer
6476

6577
override def createContextual(ctxt: DeserializationContext, property: BeanProperty): JsonDeserializer[_] = {
@@ -73,9 +85,21 @@ abstract class GenericMapFactoryDeserializerResolver[CC[K, V], CF[X[_, _]]] exte
7385
}
7486
}
7587

88+
override def deserialize(jp: JsonParser, ctxt: DeserializationContext, intoValue: CC[_, _]): CC[_, _] = {
89+
val bw = newBuilderWrapper(ctxt)
90+
bw.setInitialValue(intoValue)
91+
containerDeserializer.deserialize(jp, ctxt, bw) match {
92+
case wrapper: BuilderWrapper[_, _] => wrapper.builder.result()
93+
}
94+
}
95+
7696
override def getEmptyValue(ctxt: DeserializationContext): Object = {
77-
val bw = containerDeserializer.getValueInstantiator.createUsingDefault(ctxt).asInstanceOf[BuilderWrapper[AnyRef, AnyRef]]
97+
val bw = newBuilderWrapper(ctxt)
7898
bw.builder.result().asInstanceOf[Object]
7999
}
100+
101+
private def newBuilderWrapper(ctxt: DeserializationContext): BuilderWrapper[AnyRef, AnyRef] = {
102+
containerDeserializer.getValueInstantiator.createUsingDefault(ctxt).asInstanceOf[BuilderWrapper[AnyRef, AnyRef]]
103+
}
80104
}
81105
}

src/test/scala/com/fasterxml/jackson/module/scala/deser/DeserializerTest.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ trait DeserializerTest extends JacksonTest {
1212
def deserialize[T: Manifest](value: String) : T =
1313
newMapper.readValue(value, typeReference[T])
1414

15-
private [this] def typeReference[T: Manifest]: TypeReference[T] = new TypeReference[T] {
15+
def typeReference[T: Manifest]: TypeReference[T] = new TypeReference[T] {
1616
override def getType: Type = typeFromManifest(manifest[T])
1717
}
1818

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.fasterxml.jackson.module.scala.deser
2+
3+
import com.fasterxml.jackson.annotation.JsonMerge
4+
import com.fasterxml.jackson.module.scala.DefaultScalaModule
5+
import org.junit.runner.RunWith
6+
import org.scalatestplus.junit.JUnitRunner
7+
8+
import scala.collection.{Map, mutable}
9+
10+
case class ClassWithLists(field1: List[String], @JsonMerge field2: List[String])
11+
case class ClassWithMaps[T](field1: Map[String, T], @JsonMerge field2: Map[String, T])
12+
case class ClassWithMutableMaps[T](field1: mutable.Map[String, T], @JsonMerge field2: mutable.Map[String, T])
13+
14+
case class Pair(first: String, second: String)
15+
16+
@RunWith(classOf[JUnitRunner])
17+
class MergeTest extends DeserializerTest {
18+
19+
val module: DefaultScalaModule.type = DefaultScalaModule
20+
21+
behavior of "The DefaultScalaModule when reading for updating"
22+
23+
it should "merge both lists" in {
24+
val initial = deserialize[ClassWithLists](classJson(firstListJson))
25+
val result = newMapper.setDefaultMergeable(true)
26+
.readerForUpdating(initial).readValue[ClassWithLists](classJson(secondListJson))
27+
28+
result shouldBe ClassWithLists(mergedList, mergedList)
29+
}
30+
31+
it should "merge only the annotated list" in {
32+
val initial = deserialize[ClassWithLists](classJson(firstListJson))
33+
val result = newMapper
34+
.readerForUpdating(initial).readValue[ClassWithLists](classJson(secondListJson))
35+
36+
result shouldBe ClassWithLists(secondList, mergedList)
37+
}
38+
39+
it should "merge both string maps" in {
40+
val initial = deserialize[ClassWithMaps[String]](classJson(firstStringMapJson))
41+
val result = newMapper.setDefaultMergeable(true)
42+
.readerForUpdating(initial).readValue[ClassWithMaps[String]](classJson(secondStringMapJson))
43+
44+
result shouldBe ClassWithMaps(mergedStringMap, mergedStringMap)
45+
}
46+
47+
it should "merge only the annotated string map" in {
48+
val initial = deserialize[ClassWithMaps[String]](classJson(firstStringMapJson))
49+
val result = newMapper
50+
.readerForUpdating(initial).readValue[ClassWithMaps[String]](classJson(secondStringMapJson))
51+
52+
result shouldBe ClassWithMaps(secondStringMap, mergedStringMap)
53+
}
54+
55+
it should "merge both pair maps" in {
56+
val initial = deserialize[ClassWithMaps[Pair]](classJson(firstPairMapJson))
57+
val result = newMapper.setDefaultMergeable(true)
58+
.readerForUpdating(initial).forType(typeReference[ClassWithMaps[Pair]]).readValue[ClassWithMaps[Pair]](classJson(secondPairMapJson))
59+
60+
result shouldBe ClassWithMaps(mergedPairMap, mergedPairMap)
61+
}
62+
63+
it should "merge only the annotated pair map" in {
64+
val initial = deserialize[ClassWithMaps[Pair]](classJson(firstPairMapJson))
65+
val result = newMapper
66+
.readerForUpdating(initial).forType(typeReference[ClassWithMaps[Pair]]).readValue[ClassWithMaps[Pair]](classJson(secondPairMapJson))
67+
68+
result shouldBe ClassWithMaps(secondPairMap, mergedPairMap)
69+
}
70+
71+
it should "merge both mutable maps" in {
72+
val initial = deserialize[ClassWithMutableMaps[String]](classJson(firstStringMapJson))
73+
val result = newMapper.setDefaultMergeable(true)
74+
.readerForUpdating(initial).readValue[ClassWithMutableMaps[String]](classJson(secondStringMapJson))
75+
76+
result shouldBe ClassWithMutableMaps(mutable.Map() ++ mergedStringMap, mutable.Map() ++ mergedStringMap)
77+
}
78+
79+
it should "merge only the annotated mutable map" in {
80+
val initial = deserialize[ClassWithMutableMaps[String]](classJson(firstStringMapJson))
81+
val result = newMapper
82+
.readerForUpdating(initial).readValue[ClassWithMutableMaps[String]](classJson(secondStringMapJson))
83+
84+
result shouldBe ClassWithMutableMaps(mutable.Map() ++ secondStringMap, mutable.Map() ++ mergedStringMap)
85+
}
86+
87+
def classJson(nestedJson: String) = s"""{"field1":$nestedJson,"field2":$nestedJson}"""
88+
89+
val firstListJson = """["one","two"]"""
90+
val secondListJson = """["three"]"""
91+
val secondList = List("three")
92+
val mergedList = List("one", "two", "three")
93+
94+
val firstStringMapJson = """{"one":"1","two":"2"}"""
95+
val secondStringMapJson = """{"two":"22","three":"33"}"""
96+
val secondStringMap = Map("two" -> "22", "three" -> "33")
97+
val mergedStringMap = Map("one" -> "1", "two" -> "22", "three" -> "33")
98+
99+
val firstPairMapJson = """{"one":{"first":"1"},"two":{"second":"2"},"three":{"first":"3","second":"4"}}"""
100+
val secondPairMapJson = """{"two":{"first":"22"},"three":{"second":"33"}}"""
101+
val secondPairMap = Map("two" -> Pair("22", null), "three" -> Pair(null, "33"))
102+
val mergedPairMap = Map("one" -> Pair("1", null), "two" -> Pair("22", "2"), "three" -> Pair("3", "33"))
103+
}

0 commit comments

Comments
 (0)