I am attempting to migrate a Rails/Mongodb application to Play 2.3 using play-reactivemongo and reactivemongo-extensions. In modeling my data I am running across a problem serializing and deserializing a Map[Int,Boolean].
我正在尝试使用play-reactivemongo和reactivemongo-extensions将Rails / Mongodb应用程序迁移到Play 2.3。在建模我的数据时,我遇到了一个问题,即序列化和反序列化Map [Int,Boolean]。
When I try to define my formats via macro like so
当我尝试通过宏来定义我的格式时
implicit val myCaseClass = Json.format[MyCaseClass]
where MyCaseClass has a few string fields, a BSONObjectID field, and a Map[Int,Boolean] field the compiler complains with:
其中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.
Looking at the source code for Play in Reads.scala I see a Reads defined for Map[String,_] but none for Map[Int,_].
查看Play in Reads.scala中的Play源代码,我看到为Map [String,_]定义了一个Read,但没有为Map [Int,_]定义。
Is there a reason why Play has default Read/Writes for string maps but not for other simple types?
Play是否有字符串映射的默认读/写而不是其他简单类型的原因?
I don't fully understand the Map[String,_] defined by play because I am fairly new to scala. How would I go about translating that into a Map[Int,_]? If that is not possible for some technical reason how would I define a Reads/Writes for Map[Int,Boolean]?
我不完全理解play定义的Map [String,_],因为我对scala相当新。我如何将其转换为Map [Int,_]?如果由于某些技术原因这是不可能的,我如何定义Map [Int,Boolean]的读/写?
4 个解决方案
#1
12
you can write your own reads and writes in play.
你可以在游戏中编写自己的读写。
in your case, this would look like this:
在你的情况下,这将是这样的:
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)
I have tested it with play 2.3. I'm not sure if it's the best approach to have a Map[Int, Boolean] on server side and a json object with string -> boolean mapping on the client side, though.
我用play 2.3进行了测试。我不确定这是否是在服务器端使用Map [Int,Boolean]和在客户端使用字符串 - >布尔映射的json对象的最佳方法。
#2
6
JSON only allows string keys (a limitation it inherits from JavaScript).
JSON只允许字符串键(它从JavaScript继承的限制)。
#3
1
Thanks to Seth Tisue. This is my "generics" (half) way.
感谢Seth Tisue。这是我的“仿制药”(一半)方式。
"half" because it does not handle a generic key. one can copy paste and replace the "Long" with "Int"
“half”因为它不处理通用密钥。一个人可以复制粘贴并用“Int”替换“Long”
"Summary" is a type I've wanted to serialize (and it needed its own serializer)
“摘要”是我想要序列化的类型(它需要自己的序列化程序)
/** 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]
This is the required implementation:
这是必需的实现:
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)
}
#4
0
We can generalize the solution of 3x14159265 and Seth Tisue thanks to 2 small type classes:
由于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)
}
Note that I use Simulacrum (https://github.com/mpilquist/simulacrum) to define my type classes.
请注意,我使用Simulacrum(https://github.com/mpilquist/simulacrum)来定义我的类型类。
Here is an example of how to use it:
以下是如何使用它的示例:
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)
}
if IntelliJ says that your import MapFormat._
is never used, you can and this: implicitly[Format[Map[UserId, Something]]]
just below the import. It'll fix the pb. ;)
如果IntelliJ说您的导入MapFormat._从未使用过,您可以这样:隐式[在导入下方[格式[Map [UserId,Something]]]。它会修复pb。 ;)
#1
12
you can write your own reads and writes in play.
你可以在游戏中编写自己的读写。
in your case, this would look like this:
在你的情况下,这将是这样的:
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)
I have tested it with play 2.3. I'm not sure if it's the best approach to have a Map[Int, Boolean] on server side and a json object with string -> boolean mapping on the client side, though.
我用play 2.3进行了测试。我不确定这是否是在服务器端使用Map [Int,Boolean]和在客户端使用字符串 - >布尔映射的json对象的最佳方法。
#2
6
JSON only allows string keys (a limitation it inherits from JavaScript).
JSON只允许字符串键(它从JavaScript继承的限制)。
#3
1
Thanks to Seth Tisue. This is my "generics" (half) way.
感谢Seth Tisue。这是我的“仿制药”(一半)方式。
"half" because it does not handle a generic key. one can copy paste and replace the "Long" with "Int"
“half”因为它不处理通用密钥。一个人可以复制粘贴并用“Int”替换“Long”
"Summary" is a type I've wanted to serialize (and it needed its own serializer)
“摘要”是我想要序列化的类型(它需要自己的序列化程序)
/** 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]
This is the required implementation:
这是必需的实现:
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)
}
#4
0
We can generalize the solution of 3x14159265 and Seth Tisue thanks to 2 small type classes:
由于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)
}
Note that I use Simulacrum (https://github.com/mpilquist/simulacrum) to define my type classes.
请注意,我使用Simulacrum(https://github.com/mpilquist/simulacrum)来定义我的类型类。
Here is an example of how to use it:
以下是如何使用它的示例:
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)
}
if IntelliJ says that your import MapFormat._
is never used, you can and this: implicitly[Format[Map[UserId, Something]]]
just below the import. It'll fix the pb. ;)
如果IntelliJ说您的导入MapFormat._从未使用过,您可以这样:隐式[在导入下方[格式[Map [UserId,Something]]]。它会修复pb。 ;)