我正在尝试使用play-reactivemongo和reactivemongo-extensions将Rails / Mongodb应用程序迁移到Play 2.3。在建模我的数据时,我遇到了一个问题,即序列化和反序列化Map [Int,Boolean]。
当我尝试通过宏来定义我的格式时如此
implicit val myCaseClass = Json.format[MyCaseClass]
其中MyCaseClass有一些字符串字段,BSONObjectID字段和编译器抱怨的Map [Int,Boolean]字段:
No Json serializer found for type Map[Int,Boolean]. Try to implement an implicit Writes or Format for this type.
No Json deserializer found for type Map[Int,Boolean]. Try to implement an implicit Reads or Format for this type.
在Reads.scala中查看Play的源代码,我看到为Map [String,_]定义了一个Read,但没有为Map [Int,_]定义。
Play是否有字符串映射的默认读/写,而其他简单类型没有?
我不完全理解play定义的Map [String,_],因为我对scala相当新。我如何将其转换为Map [Int,_]?如果由于某些技术原因这是不可能的,我如何定义Map [Int,Boolean]的读/写?
答案 0 :(得分:15)
你可以在游戏中编写自己的读写。
在你的情况下,这将是这样的:
implicit val mapReads: Reads[Map[Int, Boolean]] = new Reads[Map[Int, Boolean]] {
def reads(jv: JsValue): JsResult[Map[Int, Boolean]] =
JsSuccess(jv.as[Map[String, Boolean]].map{case (k, v) =>
Integer.parseInt(k) -> v .asInstanceOf[Boolean]
})
}
implicit val mapWrites: Writes[Map[Int, Boolean]] = new Writes[Map[Int, Boolean]] {
def writes(map: Map[Int, Boolean]): JsValue =
Json.obj(map.map{case (s, o) =>
val ret: (String, JsValueWrapper) = s.toString -> JsBoolean(o)
ret
}.toSeq:_*)
}
implicit val mapFormat: Format[Map[Int, Boolean]] = Format(mapReads, mapWrites)
我用play 2.3进行了测试。我不确定它是否是服务器端的Map [Int,Boolean]和带字符串的json对象的最佳方法 - >但是,客户端的布尔映射。
答案 1 :(得分:7)
JSON只允许字符串键(它从JavaScript继承的限制)。
答案 2 :(得分:2)
感谢Seth Tisue。这是我的“仿制药”(一半)方式。
“half”因为它不处理通用密钥。可以复制粘贴并将“Long”替换为“Int”
“摘要”是我想要序列化的类型(它需要它自己的类型) 串行器)
/** this is how to create reader and writer or format for Maps*/
// implicit val mapReads: Reads[Map[Long, Summary]] = new MapLongReads[Summary]
// implicit val mapWrites: Writes[Map[Long, Summary]] = new MapLongWrites[Summary]
implicit val mapLongSummaryFormat: Format[Map[Long, Summary]] = new MapLongFormats[Summary]
这是必需的实施:
class MapLongReads[T]()(implicit reads: Reads[T]) extends Reads[Map[Long, T]] {
def reads(jv: JsValue): JsResult[Map[Long, T]] =
JsSuccess(jv.as[Map[String, T]].map{case (k, v) =>
k.toString.toLong -> v .asInstanceOf[T]
})
}
class MapLongWrites[T]()(implicit writes: Writes[T]) extends Writes[Map[Long, T]] {
def writes(map: Map[Long, T]): JsValue =
Json.obj(map.map{case (s, o) =>
val ret: (String, JsValueWrapper) = s.toString -> Json.toJson(o)
ret
}.toSeq:_*)
}
class MapLongFormats[T]()(implicit format: Format[T]) extends Format[Map[Long, T]]{
override def reads(json: JsValue): JsResult[Map[Long, T]] = new MapLongReads[T].reads(json)
override def writes(o: Map[Long, T]): JsValue = new MapLongWrites[T].writes(o)
}
答案 3 :(得分:1)
我们可以通过2个小型类来概括3x14159265和Seth Tisue的解决方案:
import play.api.libs.json.Json.JsValueWrapper
import play.api.libs.json._
import simulacrum._
object MapFormat {
@typeclass trait ToString[A] {
def toStringValue(v: A): String
}
@typeclass trait FromString[A] {
def fromString(v: String): A
}
implicit final def mapReads[K: FromString, V: Reads]: Reads[Map[K, V]] =
new Reads[Map[K, V]] {
def reads(js: JsValue): JsResult[Map[K, V]] =
JsSuccess(js.as[Map[String, V]].map { case (k, v) => FromString[K].fromString(k) -> v })
}
implicit final def mapWrites[K: ToString, V: Writes]: Writes[Map[K, V]] =
new Writes[Map[K, V]] {
def writes(map: Map[K, V]): JsValue =
Json.obj(map.map {
case (s, o) =>
val ret: (String, JsValueWrapper) = ToString[K].toStringValue(s) -> o
ret
}.toSeq: _*)
}
implicit final def mapFormat[K: ToString: FromString, V: Format]: Format[Map[K, V]] = Format(mapReads, mapWrites)
}
请注意,我使用Simulacrum(https://github.com/mpilquist/simulacrum)来定义我的类型类。
以下是如何使用它的示例:
final case class UserId(value: String) extends AnyVal
object UserId {
import MapFormat._
implicit final val userToString: ToString[UserId] =
new ToString[UserId] {
def toStringValue(v: UserId): String = v.value
}
implicit final val userFromString: FromString[UserId] =
new FromString[UserId] {
def fromString(v: String): UserId = UserId(v)
}
}
object MyApp extends App {
import MapFormat._
val myMap: Map[UserId, Something] = Map(...)
Json.toJson(myMap)
}
如果IntelliJ说您的import MapFormat._
从未使用过,那么您可以这样:implicitly[Format[Map[UserId, Something]]]
就在导入之下。它会修复pb。 ;)
答案 4 :(得分:1)
Play Json 提供了用于读写地图的内置 mapReads
和 mapWrites
。
mapReads
需要一个 (String => JsResult[K])
来让您将密钥转换为您的自定义类型。
mapWrites
返回 Writes[Map[String, Boolean]]
,您可以使用 contramap
将该编写器修改为与 Map[Int, Boolean]
一起使用的编写器
import play.api.libs.json.{JsResult, Reads, Writes}
import scala.util.Try
import play.api.libs.json.Reads.mapReads
import play.api.libs.json.MapWrites.mapWrites
object MapExample {
implicit val reads: Reads[Map[Int, Boolean]] =
mapReads[Int, Boolean](s => JsResult.fromTry(Try(s.toInt)))
implicit val writes: Writes[Map[Int, Boolean]] =
mapWrites[Boolean].contramap(_.map { case (k, v) => k.toString -> v})
}
答案 5 :(得分:0)
就像接受的答案一样-短一点:
implicit val mapReads: Reads[Map[Int, Boolean]] = (jv: JsValue) =>
JsSuccess(jv.as[Map[String, Boolean]].map { case (k, v) =>
k.toInt -> v
})
implicit val mapWrites: Writes[Map[Int, Boolean]] = (map: Map[Int, Boolean]) =>
Json.toJson(map.map { case (s, o) =>
s.toString -> o
})
implicit val jsonMapFormat: Format[Map[Int, Boolean]] = Format(mapReads, mapWrites)
这里有一点测试:
val json = Json.toJson(Map(1 -> true, 2 -> false))
println(json) // {"1":true,"2":false}
println(json.validate[Map[Int, Boolean]]) // JsSuccess(Map(1 -> true, 2 -> false),)
答案 6 :(得分:0)
https://gist.github.com/fancellu/0bea53f1a1dda712e179892785572ce3
这是一种持久化Map [NotString,...]
的方法答案 7 :(得分:0)
特定的 KeyWrites
和 KeyReads
在 play-json 2.9.x 中可用
private implicit val longKeyWrites = KeyWrites[Int](_.toString)
private implicit val longKeyReads =
KeyReads[Int](str => Try(str.toInt).fold(e => JsError(e.getMessage), JsSuccess(_)))
Json.obj("1" -> "test").validate[Map[Int,String]] // JsSuccess(Map(1 -> test))