scalaplayframeworkakkaplay-json

No instance of play.api.libs.json.Format is available for akka.actor.typed.ActorRef[org.knoldus.eventSourcing.UserState.Confirmation]


No instance of play.api.libs.json.Format is available for akka.actor.typed.ActorRef[org.knoldus.eventSourcing.UserState.Confirmation] in the implicit scope (Hint: if declared in the same file, make sure it's declared before)

[error] implicit val userCommand: Format[AddUserCommand] = Json.format

I am getting this error even though I have made Implicit instance of Json Format for AddUserCommand.

Here is my code:

trait UserCommand extends CommandSerializable
    
object AddUserCommand{
  implicit val format: Format[AddUserCommand] = Json.format[AddUserCommand]
}
    
final case class AddUserCommand(user:User, reply: ActorRef[Confirmation]) extends UserCommand

Can anyone please help me with this error and how to solve it?


Solution

  • As Gael noted, you need to provide a Format for ActorRef[Confirmation]. The complication around this is that the natural serialization, using the ActorRefResolver requires that an ExtendedActorSystem be present, which means that the usual approaches to defining a Format in a companion object won't quite work.

    Note that because of the way Lagom does dependency injection, this approach doesn't really work in Lagom: commands in Lagom basically can't use Play JSON.

    import akka.actor.typed.scaladsl.adapter.ClassicActorSystemOps
    import play.api.libs.json._
    
    class PlayJsonActorRefFormat(system: ExtendedActorSystem) {
      def reads[A] = new Reads[ActorRef[A]] {
        def reads(jsv: JsValue): JsResult[ActorRef[A]] =
          jsv match {
            case JsString(s) => JsSuccess(ActorRefResolver(system.toTyped).resolveActorRef(s))
            case _ => JsError(Seq(JsPath() -> Seq(JsonValidationError(Seq("ActorRefs are strings"))))) // hopefully parenthesized that right...
          }
      }
    
      def writes[A] = new Writes[ActorRef[A]] {
        def writes(a: ActorRef[A]): JsValue = JsString(ActorRefResolver(system.toTyped).toSerializationFormat(a))
      }
    
      def format[A] = Format[ActorRef[A]](reads, writes)
    }
    

    You can then define a format for AddUserCommand as

    object AddUserCommand {
      def format(arf: PlayJsonActorRefFormat): Format[AddUserCommand] = {
        implicit def arfmt[A]: Format[ActorRef[A]] = arf.format
    
        Json.format[AddUserCommand]
      }
    }
    

    Since you're presumably using JSON to serialize the messages sent around a cluster (otherwise, the ActorRef shouldn't be leaking out like this), you would then construct an instance of the format in your Akka Serializer implementation.

    (NB: I've only done this with Circe, not Play JSON, but the basic approach is common)