2

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.

takteek
  • 7,020
  • 2
  • 39
  • 70
  • 1
    Implicits are a compile time feature, there is little sense in matching them on objects for runtime behavior. All you need to do is require the method takes an implicit evidence for `T`. If it doesn't find one, that compiler will tell you at compile time. – Yuval Itzchakov Aug 05 '17 at 07:08
  • The problem is that the object I'm matching is Any and I have 20+ implicit Writes. I want to write one case for "an object that is one of the classes for which an implicit Writes exists". That should be possible at compile time, right? It's just a shorter version of enumerating 20+ types myself – takteek Aug 05 '17 at 18:12
  • 1
    Possible duplicate of [Test if implicit conversion is available](https://stackoverflow.com/questions/5717868/test-if-implicit-conversion-is-available) – Assaf Mendelson Aug 05 '17 at 19:09
  • I tried expanding the question a little. I don't see how the linked question answers mine... Apologies if I'm being dense. I'm still learning Scala. – takteek Aug 05 '17 at 20:34
  • I found another question which mine does actually duplicate but the answer is not what I was hoping for https://stackoverflow.com/questions/20015658/play-2-2-match-if-there-exists-an-implicit-json-converter – takteek Aug 05 '17 at 20:58

1 Answers1

2

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))

Community
  • 1
  • 1
Jacob Wang
  • 4,411
  • 5
  • 29
  • 43