【问题标题】:Get concrete type of a case class获取案例类的具体类型
【发布时间】:2015-08-11 16:18:20
【问题描述】:

我有这个:

sealed trait Block

sealed case class Header(param1: String,
                                      param2: String,
                                      ...) extends Block

... 
(more sealed case classes that follows the same pattern)

稍后,我将这些块分组到一个 Seq 中,如下所示:

val blocks: Seq[Block with Product with Serializable] = Seq(structure.header, structure.body, ...)

我想将每个块序列化为 Json(使用 Play)。我这样做如下:

blocks.map{
      x =>
          serializeBlock(x)
    }

“serializeBlock”的定义:

def serializeBlock[A<:Block](block:A): String = {
    block match {
        case block: Header => Json.toJson(block).toString()
        case block: Body => Json.toJson(block).toString()
        ... n-times for every possible case class that mixes the block trait
    }
}

我对每个具体块(标题、正文...)都有读取器和写入器,但是,如您所见,当我在 Seq 中混合这些块时,Scala 将其视为通用类型块,因此我正在做与每种可能的块类型匹配的模式(隐式转换?)。如果我只是在 Map Play 中调用“Json.toJson”,则会抱怨找不到“块”类型的读取器/写入器。

“块”是相对较大的 JSON 的片段。我正在获取 JSON,我将其拆分为合格的“块”,然后将其作为字符串保存在数据库中:

“大”JSON

{
    "header" : {
        "param1" : "",
        ...
    },
    "body" : {
        "param1" : "",
        ...
    }
    ...
}

{
    "param1" : "",
    ...
}

我的问题是:有没有办法在不重复 n 次“block: type”模式的情况下进行序列化?我的意思是:有没有办法获得该块的具体类型(知道 Seq 被键入为超类“块”而不是该块的“具体”类型)?

编辑

我为每个块都有一个读取器/写入器,如下所示:

implicit val headerReader: Reads[Header] = (
    (JsPath \ "param1").read[String] and
    (JsPath \ "param2").read[String] and
    ...
)(Header.apply _)

implicit val headerWriter: Writes[Header] = (
    (JsPath \ "param1").write[String] and
    (JsPath \ "param2").write[String] and
    ...
)(unlift(Header.unapply))

编辑 2:

Shapeless 是解决这个问题的方法吗?

编辑 3:

正如 Andrzej Jozwik 所说:“param1”和“param2”是我在这里用来定义我的 JSON 结构的“通配符”参数。每个块都有不同的参数。

【问题讨论】:

  • block的case有什么不同的行为吗?
  • @SergeyLagutin 不,每个案例类只是一个用于转换传入/传出 JSON 的容器。
  • 我的意思是模式匹配中的情况彼此不同?或者除了类型之外它们是否相同?
  • @SergeyLagutin 哦,对不起。是的,每个案例都做同样的事情 (Json.toJson(x).toString())。唯一的区别是每个块的类型。
  • 如果每个 Block 都包含相同的字段 (param1,param2) - 你不需要很多类型。您需要转换为 Map[String,Block]。

标签: json scala playframework play-json


【解决方案1】:

shapeless' HList 对您来说似乎是一个可能的解决方案。这是一个看起来非常接近您想要做的示例:

import shapeless._

sealed trait Block
case class Test1(a: String, b: String) extends Block
object Test1 {
  implicit val writes = (
    (JsPath \ "a").write[String] and
    (JsPath \ "b").write[String]
  )(unlift(Test1.unapply))
}

case class Test2(c: String, d: String) extends Block
object Test2 {
  implicit val writes =(
    (JsPath \ "c").write[String] and
    (JsPath \ "d").write[String]
  )(unlift(Test2.unapply))
}

object serializeBlock extends Poly1 {
  implicit def default[T <: Block](implicit w: Writes[T]) = at[T] { x =>
    Json.toJson(x).toString
  }
}

val blocks = Test1("hi", "hello") :: Test2("what", "why") :: HNil

blocks.map(serializeBlock).toList // List[String]({"a": "hi", "b": "hello"}, {"c": "what", "d": "why"})

请注意,HList 的每个成员都必须具有可用于该类型的隐式写入。如果不是,您得到的错误不会有太大帮助:

val list = Test1("hi", "hello") :: Test2("a", "b") :: NoWrites("wrong") :: HNil
list.map(serializeBlock)

出现错误:

could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper[serializeBlock.type,shapeless.::[Test1,shapeless.::[Test2,shapeless.::[NoWrites,shapeless.HNil]]]]

这只是意味着不能为您调用 map 的 HList 的某些成员调用 serializeBlock。

在这种情况下,它不能在 NoWrites 对象上调用 serializeBlock,因为在当前范围内没有可用的隐式 Writes[NoWrites]。

如果 HList 中的任何对象没有扩展 Block,您将得到类似的错误。

【讨论】:

    猜你喜欢
    • 2018-04-28
    • 2011-08-20
    • 2022-08-24
    • 1970-01-01
    • 2014-12-17
    • 1970-01-01
    • 1970-01-01
    • 2017-04-25
    • 2018-07-13
    相关资源
    最近更新 更多