abhishek abhishek - 1 month ago 14
Scala Question

How to define implicit Writes in trait

I have multiple case classes representing values in DB for ex User which saves user based properties like name / age / address and CallLog which saves timestamp / status_of_call

What i want to achieve

I want to have a helper function which accepts list of models and checks if the list is empty then returns "error" otherwise should return json array of the list.

My Approach

I want to have a trait which groups certain models in it and the helper method will accept either the trait or List of it in order to check or may be have a generic which implements the trait.

Problem

Since implicit writes are tightly coupled with the model class, compiler throws the error on the line

Json.toJson(list)


Things i have tried
Kept implicit in trait and got recursive type error

I am scala noob pardon me if this sounds silly
Thanks in advance

Answer

Since User, CallLog, etc. will be serialized differently, Each Writes[T] will be different for each implementation of your Model trait, so a Writes[Model] has to know about the implementation it is trying to serialize.

It is therefore not possible to have it part of the Model trait, because this information isn't known yet when you define it.

A workaround in your case would be to define your Writes[Model] in the scope of your helper function instead.

An implementation of your helper function could be like this :

import play.api.libs.json.{JsValue, Json, Writes}

sealed trait Model

case class User(name: String, age: String, address: String) extends Model
object User {
  implicit val userWrites = Json.writes[User]
}
case class CallLog(timestamp: String, status_of_call: String) extends Model
object CallLog {
  implicit val callLogWrites = Json.writes[CallLog]
}

implicit val modelWrites = new Writes[Model] {
  override def writes(o: Model): JsValue = o match {
    case u: User => Json.toJson(u)
    case cl: CallLog => Json.toJson(cl)
  }
}

def helper(models: Model*): Either[JsValue, String] = models match {
  case Nil => Right("Error")
  case _ => Left(Json.toJson(models))
}

helper(User("John", "32", "..."))
helper(User("John", "32", "..."), CallLog("now", "In progress"))