takteek
takteek

Reputation: 7110

Check if an implicit exists in a match case

The Play Framework provides a method of converting objects to JSON via an implicit Writes.

def toJson[T](o: T)(implicit tjs: Writes[T]): JsValue

I have a partial function that is processing messages, some of which may be serializable to JSON if a Writes exists. How can I write a case that matches objects for which an implicit Writes exists?

override def receive = {
  case obj:/*(T where there exists an implicit Writes[T])*/ =>
    return Json.toJson(obj)
  case other =>
    return Json.obj("unknown" -> other.toString)
}

Essentially what I want is a shorter version of

case obj:ClassA =>
  return Json.toJson(obj)
case obj:ClassB =>
  return Json.toJson(obj)
case obj:ClassC =>
  return Json.toJson(obj)
// ... repeat 20 times for all the classes where I know I have a Writes

I tried making like an unapply that accepts an implicit but I can't get case to accept that without a syntax error.

Upvotes: 2

Views: 1324

Answers (1)

Jacob Wang
Jacob Wang

Reputation: 4804

You have two options:

1. Require all Writes[ClassA], Writes[ClassB], etc in your function

You might have to define another partial function and have receive call that since you don't have the signature

2. Use the Writes instance directly to convert to Json

// companion object of ClassA
object ClassA {
  val jsonWrites: Writes[ClassA]
}

And since inside the case matching scope you know it's a ClassA, you can use the Writes[ClassA] instance directly and everything will typecheck.

case obj: ClassA =>
  return ClassA.jsonWrites.writes(obj)

EDIT: A third option that is both less error prone and flexible is to define a list of functions that 1. check whether an obj: Any is an instance of T and if so converts it to Json.

Here's a minimal snippet I have working using Scala worksheet. See checkIsInstanceAndConvertToJson:

import scala.reflect.ClassTag

case class Json(str: String)

trait Writes[A] {
  def writes(obj: A): Json
}

// Generate a function that
// 1. Checks whether an object (Any) is an instance of `T`
// 2. Convert it to Json if it is
// The implementation of checking function. All unsafety is encapsulated
def checkIsInstanceAndConvertToJson[T: Writes](implicit t: ClassTag[T]): Any => Option[Json] = (obj: Any) => {
  if (t.runtimeClass.isInstance(obj)) {
    Some(implicitly[Writes[T]].writes(obj.asInstanceOf[T]))
  }
  else None
}

// ==========
// USAGE
// ==========

object Foo {
  implicit val jsonWrites = new Writes[Foo] {
    override def writes(obj: Foo) = Json("Foo")
  }
}

class Foo

object Bar {
  implicit val jsonWrites: Writes[Bar] = new Writes[Bar] {
    override def writes(obj: Bar) = Json("Bar")
  }
}

class Bar


// Defining a list of functions that checks whether
// an object is of an instance, and if so, converts it to Json
val checkFuncs = Vector[Any => Option[Json]](
  checkIsInstanceAndConvertToJson[Bar],
  checkIsInstanceAndConvertToJson[Foo]
)

val t: Any = new Bar

val iter = checkFuncs.iterator

var json: Option[Json] = None
while (json.isEmpty && iter.hasNext) {
  val func = iter.next()
  json = func(t)
}

println(json)

The above snippets prints Some(Json(Bar))

Upvotes: 2

Related Questions