【问题标题】:Play JSON formatter for Map[Int,_]为 Map[Int,_] 播放 JSON 格式化程序
【发布时间】:2015-02-01 20:16:24
【问题描述】:

我正在尝试使用 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,_] 定义了一个读取,但没有为 Map[Int,_] 定义一个读取。

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

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

【问题讨论】:

    标签: json scala playframework playframework-2.3


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

      Play Json 提供了内置的mapReadsmapWrites 用于读写Maps。

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

      【讨论】:

        【解决方案3】:

        【讨论】:

          【解决方案4】:

          喜欢接受的答案 - 有点短:

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

          【讨论】:

            【解决方案5】:

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

            【讨论】:

            • UserId 的 FromString 实例 val 名称中有一个小错字,导致无法编译。
            【解决方案6】:

            感谢赛斯·蒂苏。这是我的“泛型”(一半)方式。

            "half" 因为它不处理通用键。可以复制粘贴并将“Long”替换为“Int”

            “Summary”是我想要序列化的类型(它需要自己的 序列化器)

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

            【讨论】:

              【解决方案7】:

              您可以在游戏中编写自己的读写操作。

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

              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 对象是否是最佳方法。

              【讨论】:

                【解决方案8】:

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

                【讨论】:

                • 如果我停下来想一想我会意识到我已经知道了哈哈。只是我很密集的片刻,对不起。无论如何,是否可以定义读/写,以便 scala 对象将具有 Map[Int,Boolean] 但使用字符串键写入 JSON 对象?从本质上解析所有 JSON 键中的整数以生成 Map[Int,_]?
                • 我建议你回去改进你的问题。希望其他人(更了解 Play)会回答。
                猜你喜欢
                • 2018-02-10
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-12-18
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多