Как динамически отправлять на основе обогащения?
Библиотека Spray-JSON расширяет базовые типы Scala toJson
метод. Я хотел бы преобразовать Any
в JsValue
если есть такой сутенер для базового типа. Моя лучшая попытка работает, но многословна:
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
}
В идеале я бы предпочел что-то (невозможное), подобное этому:
def maybeJson2(any: Any): Option[JsValue] = {
if (pimpExistsFor(any))
Some(any.toJson)
else
None
}
Есть ли способ сделать это, не перечисляя каждый обогащенный тип?
2 ответа
Есть способ, но он требует много размышлений и, следовательно, является головной болью. Основная идея заключается в следующем. DefaultJsonProtocol
объект наследует кучу признаков, которые содержат неявные объекты, которые содержат write
методы. У каждого из них будет функция доступа, но вы не будете знать, как она называется. По сути, вы просто возьмете все методы, которые не принимают параметров, и вернете один объект, который имеет write
метод, который принимает класс вашего объекта и возвращает JsValue
, Если вы найдете ровно один такой метод, который возвращает один такой класс, используйте рефлексию для его вызова. В противном случае залог.
Это будет выглядеть примерно так (предупреждение, не проверено):
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)
}
}
}
Во всяком случае, вам лучше потянуть DefaultJsonProtocol
Разобрать класс в пошаговом REPL и выяснить, как надежно идентифицировать объекты, которые определяют авторов, а затем получить write
методы из них.
Я не уверен, что он подойдет вам, но есть альтернативный подход, который действительно прост и безопасен для типов.
Если вы сохранили тип аргумента (вместо использования Any
) вы можете положиться на неявное разрешение параметров, чтобы найти правильное преобразование во время компиляции:
def toJson[T:JsonFormat]( t: T ): JsValue = implicitly[JsonFormat[T]].write(t)
Вам не понадобится опция, потому что программа завершится с ошибкой во время компиляции, если вы попытаетесь передать аргумент, который не является "pimpable".