I'm a bit new to Scala and I'm trying to write a generic client for a RESTful api I would like to use. I'm able to provide concrete Reads[T]
and Writes[T]
for the specific case classes I would like to instantiate my client for, however the compiler expects to find a Reads[T]
and Writes[T]
for any type, not just the types I'm using. Some code to illustrate (I've omitted irrelevant sections):
My generic client:
class RestModule[T](resource: String, config: Config) ... with JsonSupport{
...
def create(item: T): Future[T] = {
val appId = config.apiId
val path = f"/$apiVersion%s/applications/$appId%s/$resource"
Future {
val itemJson = Json.toJson(item)
itemJson.toString.getBytes
} flatMap {
post(path, _)
} flatMap { response =>
val status = response.status
val contentType = response.entity.contentType
status match {
case Created => contentType match {
case ContentTypes.`application/json` => {
Unmarshal(response.entity).to[T]
}
case _ => Future.failed(new IOException(f"Wrong Content Type: $contentType"))
}
case _ => Future.failed(new IOException(f"HTTP Error: $status"))
}
}
...
}
JsonSupprt Trait:
trait JsonSupport {
implicit val accountFormat = Json.format[Account]
}
I'm only ever instantiating as RestModule[Account]("accounts",config)
but I get the error
Error:(36, 32) No Json serializer found for type T. Try to implement an implicit Writes or Format for this type.
val itemJson = Json.toJson(item)
^
Why does the compiler think it needs a Writes for type T when T can only ever be of type Account? Is there any way to work around this?
The reason why the compiler doesn't like what you're doing is to do with how implicit
parameters are resolved and more crucially when they are resolved.
Consider the snippet,
Object MyFunc {
def test()(implicit s: String): String = s
}
The implicit
parameter only gets resolved by the parameter when the function is called, and basically is expanded as,
MyFunc.test()(resolvedImplicit)
In your particular case you actually call the function requiring the implicit
and hence it looks for an implicit
of T
at that point in time. Since it can't find one in scope it fails to compile.
In order to solve this issue, simply add the implicit
parameter to the create
method to tell the compiler to resolve it when you call create
rather than toJson
within create
.
Furthermore we can use scala's implicit rules to get the behaviour that you want.
Let's take your trait Reads
,
trait Reads[A] {
}
object MyFunc {
def create[A](a: A)(implicit reads: Reads[A]): Unit = ???
}
as we said befeore you can call it if the implicit
is in scope. However, in this particular case where you have predefined reads we can actually put it in the companion object,
object Reads {
implicit val readsInt: Reads[Int] = ???
implicit val readsString: Reads[String] = ???
}
This way when create
is called, the user doesn't need to import or define any implicit
vals
when A
is Int
or String
because scala automatically looks in the companion object for any implicit definitions if it can't find one in the current scope.