播放Map [Int,_]的JSON格式化程序

时间:2023-01-14 18:48:15

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。 ;)