繁体   English   中英

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

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

我正在尝试使用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
}

这是我为此目的创建的自定义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")
  }
}

假设json字符串和case object的名称相同,那么有什么办法可以避免代码重复?

您可以尝试用部分函数(或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"))
}

缺点是您必须手动指定所有案例对象的列表。 您可以尝试使用反射来生成它。 也许这个问题对获取密封特质的子类会有所帮助。 然后您可以拥有:

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

请也请参阅有关将默认案例类格式添加到Spray的讨论: https : //github.com/spray/spray-json/issues/186

更新 以解决评论

是否可以为任何T型“生成”它? 我有很多这样的密封特征/案例对象枚举,并且希望将样板保持最小。

我想出了这个:

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

然后您可以像这样使用它:

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

我还尝试了Spray-json github问题中的代码,它可以像这样使用:

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

不幸的是,这要求您显式指定所有对象。 如果您希望这样,那么,我认为,我的第一个建议(不加反思)会更好。 (因为它没有反射:-))

暂无
暂无

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

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