Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialize/deserialize maps having numerical keys. Fix #125 #209

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
}
}

}
}