Сериализация/десериализация классов классов в/из 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, очень похожий на тот, который я описал выше, который поступает из внешней библиотеки.
Любая идея? Я что-то упускаю? Любая помощь будет действительно оценена... Я схожу с ума:-( Спасибо.
Ответы
Ответ 1
Комбинаторы Json не работают для одного класса case case в Play 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: Это обходное решение еще не требуется при игре 2.3.X. Макрос работает нормально.
Ответ 2
Проблема в том, что (насколько я могу судить) в платформе 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))
Ответ 3
Почему бы просто не добавить неиспользуемое поле в класс 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 _)
}