How to serialize/deserialize case classes to/from Json in Play 2.1
Asked Answered
D

3

14

I'm trying to serialize/deserialize some case classes to/from Json... and I've troubles when dealing with case classes with just one field (I'm using 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 _)
}

The code above always generates the following error message:

[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]                        ^

I know... a case class that contains just a string does not make much sense... but I need to serialize/deserialize a case class very similar to the one I described above that comes from an external library.

Any idea? Am I missing something? Any help would be really appreciated... I'm getting crazy :-( Thanks.

Drucill answered 23/2, 2013 at 15:34 Comment(1)
please refer https://mcmap.net/q/828599/-scala-playframework-json-implicit-case-class-conversion for example to convert json to scala case classTachygraphy
S
24

Json combinators doesn't work for single field case class in Play 2.1 (it should be possible in 2.2)

Pascal (writer of this API) has explained this situation here https://groups.google.com/forum/?fromgroups=#!starred/play-framework/hGrveOkbJ6U

There are some workarounds which works, like this one:

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

ps: type is a keyword in Scala, it can't be used as parameter name (but I assume it's just for this example)

edit: This workaround is not yet required with play 2.3.X. The macro works fine.

Sizable answered 23/2, 2013 at 17:4 Comment(6)
You can capture type in a JSON serialization case class by wrapping the field name in backticks. I can't figure out how to show you that in markdown because the backticks are the code blocks :)Baklava
this should work: `type` — just escape them with a backslash, like this: `\`type\``Sulphurate
I'm using Play 2.3 (Scala) and I also needed to workaournd this limitation. I don't think it's fixed yet.Utilize
I've just tested with Play 2.3.8 and it works fine with sigle-field case class. I also encourage you to check this library (github.com/jto/validation) which has more features around Json/Xml/Form validation.Sizable
@JulienLafont Could you post a gist of your working 2.3.8 example? Like Felipe, I too get errors in 2.3.8 without the workaround.Avantgarde
Thanks! I missed that you had said the macro works in 2.3.8. The long-form syntax still doesn't work with a single arg, but the macro is cleaner anyway.Avantgarde
G
4

Problem is that (as far as I can tell) the Play 2.1 framework only handles tuples starting from Tuple2. In the examples it's used like this:

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

And then to use it

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

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

In your case you can not use the and method (you only have one value) and thus get no access to that nice apply function of FunctionalBuilder#CanBuildX (where X is 1 to 22).

In order to supply something similar you can create an implicit class that provides a build method with a similar signature as that nice apply method

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

Now you can adjust your case class like this

case class MyType(tpe: String)

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

Then you can use it like this

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

println(myTypeJson)
println(Json.fromJson[MyType](myTypeJson))
Gibert answered 23/2, 2013 at 17:13 Comment(1)
The solution given by EECOLOR boils down to using 'inmap', which transforms a Format[A] to a Format[B]. It is tersely documented here. You can also use inmap directly, but EECOLOR's solution cleanly extends the JSON combinators.Gossoon
S
0

why not simply add a unused field to the case class. put a decent comment or use a field name that is self explanatory.

//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 _)  
}
Screeching answered 24/8, 2017 at 9:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.