Skip to content

Commit

Permalink
Serialize/deserialize maps having numerical keys. Fix spray#125
Browse files Browse the repository at this point in the history
  • Loading branch information
Igor Shymko authored and Igor Shymko committed Dec 2, 2016
1 parent 537cb5b commit 2e27609
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 4 deletions.
24 changes: 23 additions & 1 deletion src/main/scala/spray/json/CollectionFormats.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

package spray.json

import scala.util.{Try, Success, Failure}

trait CollectionFormats {

/**
Expand Down Expand Up @@ -62,6 +64,26 @@ trait CollectionFormats {
}
}

implicit def mapAnyValKeyFormat[K <: AnyVal : JsonFormat, V : JsonFormat] = new RootJsonFormat[Map[K, V]] {
def write(m: Map[K, V]) = JsObject {
m.map { field =>
field._1.toJson match {
case JsNumber(x) if x.isValidLong => x.toString -> field._2.toJson
case x => throw new SerializationException("Map key must be convertible to JsNumber, not '" + x + "'")
}
}
}
def read(value: JsValue) = value match {
case x: JsObject => x.fields.map { field =>
Try(JsNumber(BigDecimal(field._1))) match {
case Success(k) => (k.convertTo[K], field._2.convertTo[V])
case Failure(_) => deserializationError("Expected Map key to be deserializable to JsNumber, but got '" + field._1 + "'")
}
} (collection.breakOut)
case x => deserializationError("Expected Map as JsObject, but got " + x)
}
}

import collection.{immutable => imm}

implicit def immIterableFormat[T :JsonFormat] = viaSeq[imm.Iterable[T], T](seq => imm.Iterable(seq :_*))
Expand Down Expand Up @@ -90,4 +112,4 @@ trait CollectionFormats {
case x => deserializationError("Expected Collection as JsArray, but got " + x)
}
}
}
}
19 changes: 16 additions & 3 deletions src/test/scala/spray/json/CollectionFormatsSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,21 @@ class CollectionFormatsSpec extends Specification with DefaultJsonProtocol {
"be able to convert a JsObject to a Map[String, Long]" in {
json.convertTo[Map[String, Long]] mustEqual map
}
"throw an Exception when trying to serialize a map whose key are not serialized to JsStrings" in {
Map(1 -> "a").toJson must throwA(new SerializationException("Map key must be formatted as JsString, not '1'"))
"convert a Map[Int, String] to a JsObject" in {
Map(1 -> "a").toJson mustEqual JsObject("1" -> JsString("a"))
}
"be able to convert a JsObject to a Map[Long, Int]" in {
val jsn = JsObject("1" -> JsNumber(1), "2" -> JsNumber(2), "3" -> JsNumber(3))
val mp: Map[Long, Int] = Map(1L -> 1, 2L -> 2, 3L -> 3)

jsn.convertTo[Map[Long, Int]] mustEqual Map(1L -> 1, 2L -> 2, 3L -> 3)
jsn.convertTo[Map[Int, Long]] mustEqual Map(1 -> 1L, 2 -> 2L, 3 -> 3L)
}
"throw an Exception when trying to deserialize a map whose key are not deserialized to JsNumber" in {
JsObject("a" -> JsString("1")).convertTo[Map[Int, String]] must throwA(new DeserializationException("Expected Map key to be deserializable to JsNumber, but got 'a'"))
}
"throw an Exception when trying to serialize a map whose key are not serialized to JsNumber" in {
Map(1.5 -> "a").toJson must throwA(new SerializationException("Map key must be convertible to JsNumber, not '1.5'"))
}
}

Expand All @@ -79,4 +92,4 @@ class CollectionFormatsSpec extends Specification with DefaultJsonProtocol {
}
}

}
}

0 comments on commit 2e27609

Please sign in to comment.