2014-12-04 14 views
6

Próbuję przenieść aplikację Rails/Mongodb do wersji 2.3 przy użyciu play-reactivemongo i reactivemongo-extensions. Podczas modelowania moich danych napotykam problem polegający na serializacji i deserializacji mapy [Int, Boolean].Odtwórz formatowanie JSON dla mapy [Int, _]

Kiedy próbuję definiować moje formatów poprzez makro jak tak

implicit val myCaseClass = Json.format[MyCaseClass] 

gdzie MyCaseClass ma kilka pól smyczkowe, pole BSONObjectID i mapy [Int, Boolean] Pole kompilator narzeka:

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. 

Patrząc na kod źródłowy do odtwarzania w Reads.scala Widzę zdefiniowany Reads dla mapy [String, _], ale brak dla mapy [Int, _].

Czy jest jakiś powód, dla którego Play ma domyślne Odczyt/Zapis dla map ciągów, ale nie dla innych prostych typów?

Nie w pełni rozumiem mapę [Ciąg, _] zdefiniowaną przez grę, ponieważ jestem dość nowa w scala. Jak mam to przetłumaczyć na Mapę [Int, _]? Jeśli nie jest to możliwe z jakiegoś technicznego punktu widzenia, jak zdefiniowałbym Czytanie/Zapis dla mapy [Int, Boolean]?

Odpowiedz

12

Możesz pisać własne teksty i zapisy w grze.

w twoim przypadku będzie to wyglądać tak:

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) 

Ja testowałem go z gry 2.3. Nie jestem pewien, czy jest to najlepsze podejście do posiadania mapy [Int, Boolean] po stronie serwera i obiektu json z ciągiem -> mapowanie boolean po stronie klienta.

6

JSON zezwala tylko na klucze strunowe (ograniczenie to dziedziczy z JavaScript).

+0

Gdybym przestał myśleć przez chwilę, zrozumiałbym, że już wiedziałem o tym haha. Przez chwilę jestem gęsty, przepraszam.Niezależnie od tego, czy można zdefiniować Odczyt/Zapis tak, że obiekt scala będzie miał Mapę [Int, Boolean], ale napisze obiekt JSON za pomocą kluczy łańcuchowych? Zasadniczo przeanalizuj liczby całkowite ze wszystkich kluczy JSON, aby utworzyć mapę [Int, _]? – imagio

+0

Proponuję wrócić i poprawić swoje pytanie. Mamy nadzieję, że ktoś inny (kto wie, lepiej zagrać) odpowie. –

1

Dzięki Seth Tisue. To jest mój "rodzajowy" (pół) sposób.

"połowa", ponieważ nie obsługuje klucza ogólnego. można skopiować pasty i zastąpić „Long” z „Int”

„Podsumowanie” to rodzaj Chciałem serializacji (i sama potrzebowała serializatora)

/** 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] 

jest to wymagane realizacja:

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

Możemy uogólnić rozwiązanie 3x14159265 i Seth Tisue dzięki 2 klasy typu małych:

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 def mapReads[K, V: Reads](implicit fstc: FromString[K]): Reads[Map[K, V]] = new Reads[Map[K, V]] { 
    def reads(jv: JsValue): JsResult[Map[K, V]] = 
     JsSuccess(jv.as[Map[String, V]].map { case (k, v) => fstc.fromString(k) -> v }) 
    } 

    implicit def mapWrites[K, V: Writes](implicit tstc: ToString[K]): 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) = tstc.toStringValue(s) -> o 
      ret 
     }.toSeq: _*) 
    } 

    implicit def mapFormat[K, V: Format](implicit tstc: ToString[K], fstc: FromString[K]): Format[Map[K, V]] = 
    Format(mapReads, mapWrites) 

} 

Pamiętaj, że używam Simulacrum (https://github.com/mpilquist/simulacrum) do definiowania moich klas typów.

Oto przykład, jak z niego korzystać:

case class UserId(value: String) 
object UserId { 
    implicit val userIdToString: ToString[UserId] = new ToString[UserId] { 
    def toStringValue(v: A): String = v.value 
    } 
    implicit val userIdToString: FromString[UserId] = new FromString[UserId] { 
    def fromString(v: String): A = UserId(v) 
    } 
} 

object MyApp extends App { 

    import MapFormat._ 

    val myMap: Map[UserId, Something] = Map(...) 

    Json.toJson(myMap) 
} 

IntelliJ jeśli mówi, że import MapFormat._ nigdy nie jest używany, można i tak: implicitly[Format[Map[UserId, Something]]] tuż poniżej importu. Naprawi pb. ;)