Как сериализовать / десериализовать классы case в / из Json в Play 2.1

Я пытаюсь сериализовать / десериализовать некоторые классы case в / из Json... и у меня проблемы при работе с классами case только с одним полем (я использую Play 2.1):

import play.api.libs.json._
import play.api.libs.functional.syntax._

case class MyType(type: String)

object MyType {

  implicit val myTypeJsonWrite = new Writes[MyType] {
    def writes(type: MyType): JsValue = {
      Json.obj(
        "type" -> MyType.type
      )
    }
  }

  implicit val myTypeJsonRead = (
    (__  'type).read[String]
  )(MyType.apply _)
}

приведенный выше код всегда генерирует следующее сообщение об ошибке:

[error] /home/j3d/Projects/test/app/models/MyType.scala:34: overloaded method value read with alternatives:
[error]   (t: String)play.api.libs.json.Reads[String] <and>
[error]   (implicit r: play.api.libs.json.Reads[String])play.api.libs.json.Reads[String]
[error]  cannot be applied to (String => models.MyType)
[error]     (__  'method).read[String]
[error]                        ^

Я знаю... класс case, содержащий только строку, не имеет большого смысла... но мне нужно сериализовать / десериализовать класс case, очень похожий на тот, который я описал выше, который исходит из внешнего библиотека.

есть идеи? Я что-то упускаю? Любая помощь будет очень признательна... Я схожу с ума: - (Спасибо.

3 ответов


комбинаторы Json не работают для класса с одним полем в игре 2.1 (это должно быть возможно в 2.2)

Паскаль (автор этого API) объяснил эту ситуацию здесь https://groups.google.com/forum/?fromgroups=#!starred/play-framework/hGrveOkbJ6U

есть некоторые обходные пути, которые работают, как этот:

case class MyType(value: String)
val myTypeRead = (__ \ 'value).read[String].map(v => MyType(v)) // covariant map

ps:type является ключевым словом в Scala, его нельзя использовать в качестве имени параметра (но я предполагаю, что это только для этого пример)

edit: этот обходной путь еще не требуется с play 2.3.Х. макрос работает нормально.


проблема в том, что (насколько я могу судить) платформа Play 2.1 обрабатывает только кортежи, начиная с Tuple2. В примерах он используется следующим образом:

case class CaseClass(key1: String, key2: String)
object CaseClass {
  implicit val caseClassFormat = {
    val jsonDescription =
      (__ \ "key1").format[String] and (__ \ "key2").format[String]

    jsonDescription(CaseClass.apply _, unlift(CaseClass.unapply))
  }
}

и затем использовать его

val caseClassJson = Json.toJson(CaseClass("value1", "value2"))

println(caseClassJson)
println(Json.fromJson[CaseClass](caseClassJson))

в вашем случае вы не можете использовать and метод (у вас есть только одно значение) и, таким образом, не получите доступа к этому хорошему apply функции FunctionalBuilder#CanBuildX (где X-от 1 до 22).

чтобы предоставить что-то подобное, вы можете создать неявный класс, который предоставляет build метод с аналогичной подписью, Как это приятно apply метод

implicit class FormatBuilder[M[_], A](o: M[A]) {
  def build[B](f1: A => B, f2: B => A)(implicit fu: InvariantFunctor[M]) =
    fu.inmap[A, B](o, f1, f2)
}

теперь вы можете настроить свой класс case следующим образом

case class MyType(tpe: String)

object MyType {
  implicit val myTypeFormat =
    ((__ \ "type").format[String]) build (MyType.apply _, unlift(MyType.unapply))
}

тогда вы можете использовать его так

val myTypeJson = Json.toJson(MyType("bar"))

println(myTypeJson)
println(Json.fromJson[MyType](myTypeJson))

почему бы просто не добавить неиспользуемое поле в класс case. поместите достойный комментарий или используйте имя поля, которое не требует пояснений.

//f2 is unused field for de/serialization convenience due to limitation in play

case class SingleField(f1: String, f2: Option[String]) 
object SingleField {
   implicit val readSingleField : Reads[SingleField] = (
        (__ \ "f1").read[String] and 
           (__ \ "f2").readNullable[String])(SingleField.apply _)  
}