Reputation: 808
I want to be able to create case classes in Scala using spray-json, but define an asJson
method in the class , but I can't seem to figure out how. For example, I'd like to be able to do this:
case class Foo(bar: String) {
def toJson: JsValue = ...
}
It would be easy enough to create an implicit JSON converter:
object JsonProtocols extends DefaultJsonProtocol {
implicit val fooFormat = jsonFormat1(Foo)
}
But as far as I know, this can only be done outside of the class. I would love to find a way of declaring a JSON format and convert to JSON within the class itself.
Upvotes: 2
Views: 4909
Reputation: 6852
You could imagine doing this:
scala> import spray.json._
import spray.json._
scala> case class Foo(bar: String) {
def toJson:JsValue = JsObject( "bar" -> JsString(bar) )
}
defined class Foo
scala> Foo("bar").toJson
res2: spray.json.JsValue = {"bar":"bar"}
So far so good, but that doesn't fit into Spray's typeclass mechanism. Spray's routing DSL, for example, would give you a type error if you then tried to convert a Foo to/from a JsValue (e.g. using the route entity( as[Foo] ) { ... }
). Nor could the implicits they have already prepared for you, for types like List and Set, work with Foo:
scala> import DefaultJsonProtocol._
import DefaultJsonProtocol._
scala> List(Foo("bar")).toJson
<console>:31: error: Cannot find JsonWriter or JsonFormat type class for List[Foo]
List(Foo("bar")).toJson
because there is no JsonFormat class for them to use to convert a Foo, like the one that JsonFormat1(Foo)
would have created.
You might then think to put the format inside the Foo companion object, since an in-scope class's companion object is on the implicit search path, like so:
object Foo extends DefaultJsonProtocol {
implicit val fooFormat = jsonFormat1(Foo)
}
case class Foo(bar: String)
But because we're not finished defining Foo at that point, the compiler gives us a type error:
[error] found : Foo.type
[error] required: ? => ?
[error] Note: implicit value fooFormat is not applicable here because it comes after the application point and it lacks an explicit result type
Adding an explicit result type of RootJsonFormat[Foo]
doesn't solve the problem:
[error] found : Foo.type
[error] required: ? => Foo
[error] implicit val fooFormat:RootJsonFormat[Foo] = jsonFormat1(Foo)
The trick (thanks knutwalker!) is to explicitly pass Foo.apply
:
object Foo extends DefaultJsonProtocol {
implicit val fooFormat = jsonFormat1(Foo.apply)
}
case class Foo(bar: String)
Upvotes: 3