2

Suppose I have some case class with one field

case class Id(value: String)

Trivially, I can define a formatter by defining the Reads and Writes separately:

private implicit val idReads: Reads[Id] =
    JsPath.read[String].map(Id)

private implicit val idWrites: Writes[Id] =
{
    id: Id => JsString(id.value)
}

private idFormats: Format[Id] = Format(idReads, idWrites)

The documentation suggests there is a way to define a symmetric formatter for this scenario, but I haven't found the specific incantation which makes it work for this case. I've tried the below, but I get a compile error:

private implicit val idFormats: Format[Id] =
    JsPath.format[String](Id, unlift(Id.unapply))

Specifically, I get this compile error:

[error] overloaded method value format with alternatives:
[error]   (w: play.api.libs.json.Writes[String])(implicit r: play.api.libs.json.Reads[String])play.api.libs.json.OFormat[String] <and>
[error]   (r: play.api.libs.json.Reads[String])(implicit w: play.api.libs.json.Writes[String])play.api.libs.json.OFormat[String] <and>
[error]   (implicit f: play.api.libs.json.Format[String])play.api.libs.json.OFormat[String]
[error]  cannot be applied to (Id.type, Id => String)
[error]         JsPath.format[String](Id, unlift(Id.unapply))
[error]                      ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed
[error] Total time: 1 s, completed Nov 13, 2017 5:07:58 PM

I've read the documentation, but it hasn't helped me. I'm certain there is some one-liner that can be applied for this case, as it is trivial for case classes with two fields:

case class MyRow(id: Id, myNum: MyNum)

private implicit val myRowFormats: Format[MyRow] =
    ((JsPath \ "id").format[Id] and
        (JsPath \ "num").format[MyNum]) (MyRow, unlift(MyRow.unapply))
Will Beason
  • 2,639
  • 2
  • 19
  • 42

1 Answers1

3

If you really want Id to be serialized as a JSON string, here you go:

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

case class Id(value: String)

implicit val idFormats: Format[Id] =
  implicitly[Format[String]].inmap(Id, unlift(Id.unapply))

Json.toJson(Id("asd")) == JsString("asd")
Json.toJson(Id("asd")).toString == "\"asd\""
Json.parse(Json.toJson(Id("asd")).toString).as[Id] == Id("asd")

I wrote it this way to clearly illustrate that you use nothing else that a basic String formatter, which is defined in play-json.

Frederic A.
  • 3,329
  • 8
  • 17
  • yep, as a JsString. Thanks! The implicit function "inmap" is what I was looking for. I wish inmap was documented for 2.6, not just 2.1. – Will Beason Nov 14 '17 at 15:47