The spray-json library extends basic Scala types with a toJson
method. I'd like to convert an Any
into a JsValue
if there is such a pimp for the underlying type. My best attempt works, but is verbose:
import cc.spray._
val maybeJson1: PartialFunction[Any, JsValue] = {
case x: BigDecimal => x.toJson
case x: BigInt => x.toJson
case x: Boolean => x.toJson
case x: Byte => x.toJson
case x: Char => x.toJson
case x: Double => x.toJson
case x: Float => x.toJson
case x: Int => x.toJson
case x: Long => x.toJson
case x: Short => x.toJson
case x: String => x.toJson
case x: Symbol => x.toJson
case x: Unit => x.toJson
}
Ideally, I'd prefer something (impossible) like this:
def maybeJson2(any: Any): Option[JsValue] = {
if (pimpExistsFor(any))
Some(any.toJson)
else
None
}
Is there a way to do this without enumerating every type that has been enriched?
There is a way, but it requires a lot of reflection and therefore is quite a headache. The basic idea is as follows. The DefaultJsonProtocol
object inherits a bunch of traits that contain implicit objects which contain write
methods. Each of those will have an accessor function, but you won't know what it's called. Basically, you'll just take all methods that take no parameters and return one object that has a write
method that takes the class of your object and returns a JsValue
. If you find exactly one such method that returns one such class, use reflection to call it. Otherwise, bail.
It would look something like this (warning, untested):
def canWriteMe(writer: java.lang.Class[_], me: java.lang.Class[_]):
Option[java.lang.reflect.Method] =
{
writer.getMethods.find(_.getName == "write").filter{ m =>
classOf[JsValue].isAssignableFrom(m.getReturnType) && {
val parm = m.getParameterTypes()
m.length == 1 && parm(0).isAssignableFrom(me)
}
}
}
def maybeJson2(any: Any): Option[JsValue] = {
val couldWork = {
DefaultJsonProtocol.getClass.getMethods.
filter(_.getParameterTypes.length==0).
flatMap(m => canWriteMe(m.getReturnType, any.getClass).map(_ -> m))
}
if (couldWork.length != 1) None else {
couldWork.headOption.map{ case (wrMeth, obMeth) =>
val wrObj = obMeth.invoke(DefaultJsonProtocol)
val answer = wrMeth.invoke(wrObj, any)
}
}
}
Anyway, you're best off pulling the DefaultJsonProtocol
class apart in the REPL step by step and finding out how to reliably identify the objects that define the writers, and then get the write
methods out of them.