简体   繁体   English

用Spray-json对Scala案例对象进行Json反序列化

[英]Json deserialization of Scala case objects with spray-json

I am trying to write a custom JsonReader using spray-json for the following domain model: 我正在尝试使用spray-json为以下域模型编写自定义JsonReader

sealed trait OrderType
object OrderType {
  case object MARKET extends OrderType
  case object LIMIT extends OrderType
  case object STOP extends OrderType
  case object MARKET_IF_TOUCHED extends OrderType
  case object TAKE_PROFIT extends OrderType
  case object STOP_LOSS extends OrderType
  case object TRAILING_STOP_LOSS extends OrderType
}

Here is the custom JsonReader I created for this purpose: 这是我为此目的创建的自定义JsonReader

implicit object OrderTypeJsonReader extends JsonReader[OrderType] {
  def read(value: JsValue): OrderType = value match {
    case JsString("MARKET") => MARKET
    case JsString("LIMIT") => LIMIT
    case JsString("STOP") => STOP
    case JsString("MARKET_IF_TOUCHED") => MARKET_IF_TOUCHED
    case JsString("TAKE_PROFIT") => TAKE_PROFIT
    case JsString("STOP_LOSS") => STOP_LOSS
    case JsString("TRAILING_STOP_LOSS") => TRAILING_STOP_LOSS
    case _ => deserializationError("OrderType expected")
  }
}

Given that the json string and the name of the case object are the same, is there any way to avoid code duplication here? 假设json字符串和case object的名称相同,那么有什么办法可以避免代码重复?

You could try to replace pattern match with a partial function(or Map): 您可以尝试用部分函数(或Map)替换模式匹配:

val orderTypes = List(MARKET, LIMIT, STOP, MARKET_IF_TOUCHED, TAKE_PROFIT, STOP_LOSS, TRAILING_STOP_LOSS)

val string2orderType: Map[JsValue, OrderType] = 
  orderTypes.map(ot => (JsString(ot.toString), ot)).toMap

implicit object OrderTypeJsonReader extends JsonReader[OrderType] {
  def read(value: JsValue): OrderType = 
    string2orderType.getOrElse(value, deserializationError("OrderType expected"))
}

The disadvantage is that you have to specify the list of all case objects manually. 缺点是您必须手动指定所有案例对象的列表。 You can try to use reflection to generate it. 您可以尝试使用反射来生成它。 Maybe this question would be helpful for that Getting subclasses of a sealed trait . 也许这个问题对获取密封特质的子类会有所帮助。 Then you can have: 然后您可以拥有:

import scala.reflect.runtime.universe

private val tpe = universe.typeOf[OrderType]
private val clazz = tpe.typeSymbol.asClass

private def objectBy[T](name: String): T = Class.forName(OrderType.getClass.getName + name + "$").newInstance().asInstanceOf[T]

val string2orderType: Map[JsValue, OrderType] = clazz.knownDirectSubclasses.map { sc =>
  val objectName = sc.toString.stripPrefix("object ")
  (JsString(objectName), objectBy[OrderType](objectName))
}.toMap

implicit object OrderTypeJsonReader extends JsonReader[OrderType] {
  def read(value: JsValue): OrderType = string2orderType.getOrElse(value, deserializationError("OrderType expected"))
}

Please, also see this discussion about adding a default case class format to Spray: https://github.com/spray/spray-json/issues/186 请也请参阅有关将默认案例类格式添加到Spray的讨论: https : //github.com/spray/spray-json/issues/186

UPDATE to address the comments 更新 以解决评论

Is it possible to 'generify' it for any type T? 是否可以为任何T型“生成”它? I have quite a few of those sealed trait / case object enumerations and would prefer to have the boilerplate kept to minimum. 我有很多这样的密封特征/案例对象枚举,并且希望将样板保持最小。

I've come up with this: 我想出了这个:

import spray.json._
import Utils._

sealed trait OrderStatus
object OrderStatus {
  case object Cancelled extends OrderStatus
  case object Delivered extends OrderStatus
  // More objects...

  implicit object OrderStatusJsonReader extends ObjectJsonReader[OrderStatus]
}

sealed trait OrderType
object OrderType {
  case object MARKET extends OrderType
  case object LIMIT extends OrderType
  // More objects...

  implicit object OrderTypeJsonReader extends ObjectJsonReader[OrderType]
}

object Utils {
  import scala.reflect.ClassTag
  import scala.reflect.runtime.universe._

  def objectBy[T: ClassTag](name: String): T = {
    val c = implicitly[ClassTag[T]]
    Class.forName(c + "$" + name + "$").newInstance().asInstanceOf[T]
  }

  def string2trait[T: TypeTag : ClassTag]: Map[JsValue, T] = {
    val clazz = typeOf[T].typeSymbol.asClass
    clazz.knownDirectSubclasses.map { sc =>
      val objectName = sc.toString.stripPrefix("object ")
      (JsString(objectName), objectBy[T](objectName))
    }.toMap
  }

  class ObjectJsonReader[T: TypeTag : ClassTag] extends JsonReader[T] {
    val string2T: Map[JsValue, T] = string2trait[T]
    def defaultValue: T = deserializationError(s"${ implicitly[ClassTag[T]].runtimeClass.getCanonicalName } expected")
    override def read(json: JsValue): T = string2T.getOrElse(json, defaultValue)
  }
}

Then you can use it like: 然后您可以像这样使用它:

import OrderType._
import OrderStatus._
JsString("MARKET").convertTo[OrderType]
JsString(OrderStatus.Cancelled.toString).convertTo[OrderStatus]

I also tried code from spray-json github issue and it can be used like so: 我还尝试了Spray-json github问题中的代码,它可以像这样使用:

 implicit val orderTypeJsonFormat: RootJsonFormat[OrderType] = caseObjectJsonFormat(MARKET, LIMIT, STOP, MARKET_IF_TOUCHED, TAKE_PROFIT, STOP_LOSS, TRAILING_STOP_LOSS) 

Unfortunately, this requires you to specify all of the objects explicitly. 不幸的是,这要求您显式指定所有对象。 If you want it like so, then, I think, my first suggestion (without reflection) is better. 如果您希望这样,那么,我认为,我的第一个建议(不加反思)会更好。 (Because it is without reflection :-) ) (因为它没有反射:-))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM