Playing JSON formatting for a map [Int, _]

I am trying to port a Rails / Mongodb application to Play 2.3 using play-reactivemongo and reactivemongo-extensions. When modeling my data, I ran into the problem of serializing and deserializing Map [Int, Boolean].

When I try to define my formats using a macro, so

implicit val myCaseClass = Json.format[MyCaseClass] 

where MyCaseClass has several string fields, a BSONObjectID field and a Map [Int, Boolean] field, the compiler complains about:

 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 that Reads is defined for Map [String, _], but not for Map [Int, _].

Is there a reason why Play has a Read / Writes default value for string cards, but not other simple types?

I don’t quite understand the map [String, _] defined by the game, because I'm pretty new to scala. How do I translate this to the [Int, _] card? If this is not possible for some technical reason, how can I define Reads / Writes for Map [Int, Boolean]?

+7
source share
5 answers

You can write your own readings and entries in the game.

in your case it will 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 tested it with game 2.3. I'm not sure if this is the best approach to have Map [Int, Boolean] on the server side and a json object with a string -> boolean mapping on the client side.

+13
source

JSON only allows string keys (a limitation that it inherits from JavaScript).

+7
source

Thanks to Seth Tees. This is my "generic" (half).

half because it does not process the shared key. you can copy the paste and replace "Long" with "Int"

The "Summary" is the type that I would like 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) } 
+1
source

We can generalize the solution 3x14159265 and Seth Tisue thanks to two classes of small type:

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

Please note that I am using Simulacrum ( https://github.com/mpilquist/simulacrum ) to define classes of my type.

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._ never used, you can do this: implicitly[Format[Map[UserId, Something]]] just below the import. This will fix the PB. ;)

+1
source

The accepted answer seems to be a little shorter

 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) 

Here is a little test:

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

Source: https://habr.com/ru/post/979084/


All Articles