Skip to content
This repository was archived by the owner on Oct 13, 2025. It is now read-only.

Commit 6dbb379

Browse files
authored
issue #15 - fix inner Map classes serialization (Scala 2.13) (#19)
1 parent a9ae570 commit 6dbb379

File tree

3 files changed

+93
-18
lines changed

3 files changed

+93
-18
lines changed

src/main/scala/com/arangodb/velocypack/module/scala/VPackScalaModule.scala

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import com.arangodb.velocypack.VPackSetupContext
55
import com.arangodb.velocypack.module.scala.internal.VPackScalaSerializers
66
import com.arangodb.velocypack.module.scala.internal.VPackScalaDeserializers
77

8-
import scala.collection.immutable.{HashMap, ListMap, TreeMap}
8+
import scala.collection.mutable
9+
import scala.collection.concurrent.TrieMap
10+
import scala.collection.immutable.{HashMap, ListMap, TreeMap, TreeSeqMap, VectorMap}
911

1012
class VPackScalaModule extends VPackModule {
1113

@@ -25,31 +27,38 @@ class VPackScalaModule extends VPackModule {
2527

2628
// serializers
2729

28-
Set(
30+
Set[Class[_ <: Seq[_]]](
2931
classOf[List[Any]],
3032
classOf[Vector[Any]],
3133
classOf[Seq[Any]],
3234
Seq(()).getClass,
33-
Seq.empty.getClass,
35+
Seq.empty[Any].getClass,
3436
Nil.getClass
3537
).foreach(context.registerSerializer(_, VPackScalaSerializers.SEQ))
3638

37-
Set(
39+
Set[Class[_ <: Map[_, _]]](
3840
classOf[Map.Map1[_, _]],
3941
classOf[Map.Map2[_, _]],
4042
classOf[Map.Map3[_, _]],
4143
classOf[Map.Map4[_, _]],
4244
classOf[HashMap[_, _]],
45+
classOf[VectorMap[_, _]],
46+
classOf[TreeSeqMap[_, _]],
4347
classOf[TreeMap[_, _]],
44-
ListMap(() -> ()).getClass,
45-
classOf[Map[_, _]]
46-
).foreach(context.registerSerializer(_, VPackScalaSerializers.MAP))
48+
Map("" -> "").withDefault(null).getClass, // inner Map.WithDefault
49+
ListMap("" -> "").getClass // inner ListMap.Node
50+
).foreach(context.registerSerializer(_, VPackScalaSerializers.MAP_IMMUTABLE))
4751

48-
context.registerEnclosingSerializer(classOf[Map[Any, Any]], VPackScalaSerializers.MAP)
52+
Set[Class[_ <: mutable.Map[_, _]]](
53+
classOf[TrieMap[Any, Any]]
54+
).foreach(context.registerSerializer(_, VPackScalaSerializers.MAP_MUTABLE))
55+
56+
context.registerEnclosingSerializer(classOf[Map[Any, Any]], VPackScalaSerializers.MAP_IMMUTABLE)
57+
context.registerEnclosingSerializer(classOf[mutable.Map[Any, Any]], VPackScalaSerializers.MAP_MUTABLE)
4958

5059
context.registerSerializer(classOf[BigInt], VPackScalaSerializers.BIG_INT)
51-
context.registerSerializer(classOf[Option[Any]], VPackScalaSerializers.OPTION)
5260
context.registerSerializer(classOf[BigDecimal], VPackScalaSerializers.BIG_DECIMAL)
61+
context.registerSerializer(classOf[Option[Any]], VPackScalaSerializers.OPTION)
5362
}
5463

5564
}

src/main/scala/com/arangodb/velocypack/module/scala/internal/VPackScalaSerializers.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package com.arangodb.velocypack.module.scala.internal
33
import com.arangodb.velocypack.VPackSerializer
44
import com.arangodb.velocypack.VPackSerializationContext
55
import com.arangodb.velocypack.VPackBuilder
6+
67
import scala.collection.mutable.ListBuffer
78
import scala.jdk.CollectionConverters._
9+
import scala.collection.mutable
810

911
object VPackScalaSerializers {
1012

@@ -20,11 +22,16 @@ object VPackScalaSerializers {
2022
}
2123
}
2224

23-
val MAP = new VPackSerializer[Map[Any, Any]] {
25+
val MAP_IMMUTABLE = new VPackSerializer[Map[Any, Any]] {
2426
def serialize(builder: VPackBuilder, attribute: String, value: Map[Any, Any], context: VPackSerializationContext): Unit =
2527
context.serialize(builder, attribute, value.asJava)
2628
}
2729

30+
val MAP_MUTABLE = new VPackSerializer[mutable.Map[Any, Any]] {
31+
def serialize(builder: VPackBuilder, attribute: String, value: mutable.Map[Any, Any], context: VPackSerializationContext): Unit =
32+
context.serialize(builder, attribute, value.asJava)
33+
}
34+
2835
val BIG_INT = new VPackSerializer[BigInt] {
2936
def serialize(builder: VPackBuilder, attribute: String, value: BigInt, context: VPackSerializationContext): Unit =
3037
context.serialize(builder, attribute, value.bigInteger)

src/test/scala/com/arangodb/velocypack/module/scala/VPackMapTest.scala

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import org.scalatest.funsuite._
55
import org.scalatest.matchers._
66

77
import scala.beans.BeanProperty
8+
import scala.collection.concurrent.TrieMap
89
import scala.collection.immutable._
910

1011
case class MapTestEntity(@BeanProperty var m: Map[String, Any] = Map()) {
@@ -47,33 +48,82 @@ class VPackMapTest extends AnyFunSuite with should.Matchers {
4748
vpack.get("o").get("ss").getAsString should be("hello world")
4849
}
4950

51+
test("serialize TrieMap") {
52+
val vp = new VPack.Builder().registerModule(new VPackScalaModule).build()
53+
val vpack = vp.serialize(TrieMap("s" -> "hello world", "i" -> 69, "o" -> TrieMap("ss" -> "hello world")))
54+
vpack should not be null
55+
vpack.isObject should be(true)
56+
vpack.size should be(3)
57+
vpack.get("s").isString should be(true)
58+
vpack.get("s").getAsString should be("hello world")
59+
vpack.get("i").isInteger should be(true)
60+
vpack.get("i").getAsInt should be(69)
61+
vpack.get("o").isObject should be(true)
62+
vpack.get("o").size should be(1)
63+
vpack.get("o").get("ss").isString should be(true)
64+
vpack.get("o").get("ss").getAsString should be("hello world")
65+
}
66+
67+
test("serialize inner Map and MapLike maps") {
68+
val vp = new VPack.Builder().registerModule(new VPackScalaModule).build()
69+
70+
// dummy transformations that return inner Map classes' implementations
71+
val dummyMapOperations: Seq[Map[String, _] => Map[String, _]] = Seq(
72+
_.withDefaultValue(42),
73+
)
74+
75+
dummyMapOperations.foreach(dummyOp => {
76+
val mapLike1 = dummyOp(Map("ss" -> "hello world"))
77+
val mapLike2 = dummyOp(Map("s" -> "hello world", "i" -> 69, "o" -> mapLike1))
78+
val entity = MapTestEntity(m = mapLike2)
79+
80+
val vpack = vp.serialize(entity)
81+
82+
vpack should not be null
83+
vpack.isObject should be(true)
84+
vpack.size should be(1)
85+
vpack.get("m") should not be null
86+
vpack.get("m").isObject should be(true)
87+
vpack.get("m").size should be(3)
88+
vpack.get("m").get("s").isString should be(true)
89+
vpack.get("m").get("s").getAsString should be("hello world")
90+
vpack.get("m").get("i").isInteger should be(true)
91+
vpack.get("m").get("i").getAsInt should be(69)
92+
vpack.get("m").get("o").isObject should be(true)
93+
vpack.get("m").get("o").size should be(1)
94+
vpack.get("m").get("o").get("ss").isString should be(true)
95+
vpack.get("m").get("o").get("ss").getAsString should be("hello world")
96+
})
97+
}
5098

5199
test("serialize different kinds of nested maps") {
52100
val vp = new VPack.Builder().registerModule(new VPackScalaModule).build()
53101

54102
val entity = MapTestEntity(m =
55-
HashMap( // scala <= 2.12 -> HashTrieMap is in fact used behind the scene when Map size is >= 5
103+
HashMap(
56104
"seq" -> Seq(
57-
HashMap("foo" -> 42), // scala <= 2.12 -> Map.Map1, Map.Map2, Map.Map3, HashMap4 is in fact used behind the scene when Map size is <5
105+
VectorMap("foo" -> 42),
106+
TreeSeqMap("foo" -> 42),
107+
Map("foo" -> 42), // Map.Map1
58108
SortedMap("foo" -> 42),
59109
ListMap("foo" -> 42),
60-
HashMap("foo" -> 42),
61-
Map("foo" -> 42),
110+
TrieMap("foo" -> 42),
62111
ListMap.empty,
63112
Map.empty
64113
),
65114
"seq2" -> Seq(Map("foo" -> 42)), // Map.Map1
66115
"seq3" -> Seq(Map("foo" -> 42, "foo2" -> 42)), // Map.Map2
67116
"seq4" -> Seq(Map("foo" -> 42, "foo2" -> 42, "foo3" -> 42)), // Map.Map3
68117
"seq5" -> Seq(Map("foo" -> 42, "foo2" -> 42, "foo3" -> 42, "foo4" -> 42)), // Map.Map4
118+
"seq6" -> Seq(TreeMap("foo" -> 42))
69119
))
70120

71121
val vpack = vp.serialize(entity)
72122
vpack should not be null
73123
vpack.isObject should be(true)
74124
vpack.size should be(1)
75125
vpack.get("m").isObject should be(true)
76-
vpack.get("m").size should be(5)
126+
vpack.get("m").size should be(6)
77127
vpack.get("m").get("seq").isArray should be(true)
78128

79129
vpack.get("m").get("seq").get(0).isObject should be(true)
@@ -102,10 +152,15 @@ class VPackMapTest extends AnyFunSuite with should.Matchers {
102152
vpack.get("m").get("seq").get(4).get("foo").getAsInt should be(42)
103153

104154
vpack.get("m").get("seq").get(5).isObject should be(true)
105-
vpack.get("m").get("seq").get(5).size should be(0)
155+
vpack.get("m").get("seq").get(5).size should be(1)
156+
vpack.get("m").get("seq").get(5).get("foo").isInt should be(true)
157+
vpack.get("m").get("seq").get(5).get("foo").getAsInt should be(42)
106158

107-
vpack.get("m").get("seq").get(5).isObject should be(true)
108-
vpack.get("m").get("seq").get(5).size should be(0)
159+
vpack.get("m").get("seq").get(6).isObject should be(true)
160+
vpack.get("m").get("seq").get(6).size should be(0)
161+
162+
vpack.get("m").get("seq").get(7).isObject should be(true)
163+
vpack.get("m").get("seq").get(7).size should be(0)
109164

110165
vpack.get("m").get("seq2").get(0).isObject should be(true)
111166
vpack.get("m").get("seq2").get(0).size should be(1)
@@ -127,6 +182,10 @@ class VPackMapTest extends AnyFunSuite with should.Matchers {
127182
vpack.get("m").get("seq5").get(0).get("foo4").isInt should be(true)
128183
vpack.get("m").get("seq5").get(0).get("foo4").getAsInt should be(42)
129184

185+
vpack.get("m").get("seq6").get(0).isObject should be(true)
186+
vpack.get("m").get("seq6").get(0).size should be(1)
187+
vpack.get("m").get("seq6").get(0).get("foo").isInt should be(true)
188+
vpack.get("m").get("seq6").get(0).get("foo").getAsInt should be(42)
130189
}
131190

132191
test("deserialize map") {

0 commit comments

Comments
 (0)