Pablo Fernandez
Pablo Fernandez

Reputation: 105238

Playframeworks json Writes implicit requires explicit type, why?

This doesn't compile:

package model

import play.api.libs.json._

case class Dog(id: Long, name: String, kind: String) {

  def asJson() = Json.toJson(this)
}

object Dog {
  implicit val writes = Json.writes[Dog]
}

The error:

53. Waiting for source changes... (press enter to interrupt)
[info] Compiling 1 Scala source to /Users/pablo/projects/mvp/target/scala-2.10/classes...
[error] /Users/pablo/projects/mvp/app/models/model2.scala:7: No Json deserializer found for type model.Dog. Try to implement an implicit Writes or Format for this type.
[error]   def asJson() = Json.toJson(this)
[error]                             ^
[error] one error found
[error] (compile:compile) Compilation failed

Changing the companion object (note the explicit types):

object Dog {
  implicit val writes: Writes[Dog] = Json.writes[Dog]
}

Fixes the issue. Why is that?

Upvotes: 2

Views: 1725

Answers (3)

Manuel Bernhardt
Manuel Bernhardt

Reputation: 3124

Json.toJsonis a macro, which is executed at compile-time. At that stage, the type analysis has not yet taken place, meaning that the type of the method parameter (this) cannot be automatically inferred (or perhaps it could be inferred if the macro implemented the extra functionality required to do so, but I would suppose this to be quite difficult). This is why you need to provide a type parameter such as Json.toJson[Dog].

When you declare a companion object like so:

object Dog {
  implicit val writes: Writes[Dog] = Json.writes[Dog]
}

you declare an implicit writer for the type. Given the implicit resolution rules of Scala, the writer will be found in scope when you try to turn the case class instance into JSON. So it works, but not because of the asJson() method in your class, but because the implicit resolution has found the writer defined in the companion object.

Upvotes: 3

kipsigman
kipsigman

Reputation: 296

Macros is an experimental feature, so I wouldn't rely too heavily on it. I know this is much more verbose, but if you use the combinators it should work:

implicit val writes: Writes[Dog] = (
  (JsPath \ "id").write[Long] and
  (JsPath \ "name").write[String] and
  (JsPath \ "kind").write[String]
)(unlift(Dog.unapply))

http://www.playframework.com/documentation/2.3.x/ScalaJsonCombinators

Upvotes: 1

vptheron
vptheron

Reputation: 7466

This is not really an answer (but I need a lot of space to paste my code!) but this works for me:

package model

import play.api.libs.json._

object Dog {
  implicit val writes = Json.writes[Dog]
}

case class Dog(id: Long, name: String, kind: String) {

  def asJson() = Json.toJson(this)
}

Basically I just moved the object declaration before the class. This may be due to the fact that Json.writes uses macros and therefor has to happen before using the constructed Write instance.

Upvotes: 3

Related Questions