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.

Julien Lafont
  • 7,869
  • 2
  • 32
  • 52
j3d
  • 9,492
  • 22
  • 88
  • 172
  • 1
    please refer http://stackoverflow.com/a/20130414/1435971 for example to convert json to scala case class – prarthan Nov 21 '13 at 20:42

3 Answers3

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.

Julien Lafont
  • 7,869
  • 2
  • 32
  • 52
  • 2
    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 :) – Ian McMahon Feb 23 '13 at 17:11
  • this should work: `\`type\`` — just escape them with a backslash, like this: `\`\\`type\\`\`` – Erik Kaplun Jan 23 '15 at 11:01
  • 3
    I'm using Play 2.3 (Scala) and I also needed to workaournd this limitation. I don't think it's fixed yet. – Felipe Feb 19 '15 at 16:17
  • 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 (https://github.com/jto/validation) which has more features around Json/Xml/Form validation. – Julien Lafont Feb 20 '15 at 19:12
  • @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. – David B. Mar 25 '15 at 19:01
  • 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. – David B. Mar 26 '15 at 15:48
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))
EECOLOR
  • 11,184
  • 3
  • 41
  • 75
  • The solution given by EECOLOR boils down to using 'inmap', which transforms a `Format[A]` to a `Format[B]`. It is tersely documented [here](http://www.playframework.com/documentation/2.2.x/ScalaJsonCombinators). You can also use `inmap` directly, but EECOLOR's solution cleanly extends the JSON combinators. – rakensi Jan 06 '14 at 09:11
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 _)  
}
DevZer0
  • 13,433
  • 7
  • 27
  • 51