Как динамически отправлять на основе обогащения?

Библиотека 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".

Другие вопросы по тегам