Play JSON 格式化程序 for Map[Int,_]



我正在尝试使用play-reactivemongo和reactivemongo-extensions将Rails/Mongodb应用程序迁移到Play 2.3。在对数据进行建模时,我遇到了序列化和反序列化 Map[Int,布尔值] 的问题。

当我尝试通过这样的宏定义我的格式时

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.

查看 Play in Reads.scala 的源代码,我看到为 Map[String,_] 定义的 Reads,但没有为 Map[Int,_] 定义的 Reads。

Play 对字符串映射具有默认读/写而不是其他简单类型没有的原因是什么?

我不完全理解 play 定义的 Map[String,_],因为我对 scala 相当陌生。我将如何将其转换为地图[Int,_]?如果由于某些技术原因无法做到这一点,我将如何定义 Map[Int,布尔值] 的读/写?

你可以在游戏中编写自己的读写。

在您的情况下,这看起来像这样:

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,布尔值]并在客户端使用带有字符串 ->布尔映射的 json 对象是否是最佳方法。

JSON 只允许字符串键(它继承自 JavaScript 的限制)。

Play Json 提供了用于读取和写入地图的内置mapReadsmapWrites

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})
}

感谢 Seth Tisue。这是我的"泛型"(一半)方式。

"一半",因为它不处理泛型键。 可以复制粘贴并将"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)
}

我们可以推广 3x14159265 和 Seth Tisue 的解决方案,这要归功于 2 个小类型类:

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

play-json 2.9.x 中提供了特定的KeyWritesKeyReads

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

就像接受的答案 - 短一点:

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),)

https://gist.github.com/fancellu/0bea53f1a1dda712e179892785572ce3

这是一种持久化 Map[NotString,...] 的方法

相关内容

最新更新