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

时间:2014-12-04 03:30:14

标签: json scala playframework playframework-2.3

我正在尝试使用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]的读/写?

8 个答案:

答案 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 提供了用于读写地图的内置 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})
}

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

特定的 KeyWritesKeyReads 在 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))