带有 Play 2.2 库的密封特征的无噪声 JSON 格式

Posted

技术标签:

【中文标题】带有 Play 2.2 库的密封特征的无噪声 JSON 格式【英文标题】:Noise free JSON format for sealed traits with Play 2.2 library 【发布时间】:2013-06-05 23:43:48 【问题描述】:

我需要一个简单的 JSON 序列化解决方案,仪式最少。所以我很高兴找到this forthcoming Play 2.2 library。这与普通案例类完美配合,例如

import play.api.libs.json._

sealed trait Foo
case class Bar(i: Int) extends Foo
case class Baz(f: Float) extends Foo

implicit val barFmt = Json.format[Bar]
implicit val bazFmt = Json.format[Baz]

但以下失败:

implicit val fooFmt = Json.format[Foo]   // "No unapply function found"

我将如何为Foo 设置据称丢失的提取器?

或者您会推荐任何其他或多或少完全自动处理我的案例的独立库吗?我不在乎是在编译时使用宏还是在运行时使用反射,只要它开箱即用即可。

【问题讨论】:

是否缺少某些代码?是唯一定义Foosealed trait Foo 行吗?那你期望会发生什么?我想如果apply()unapply() 方法,Json.format 将适用于常规课程。 播放json,以及lift json应该没问题。您会看到,您正在尝试获取特征的格式,但几乎所有提供透明序列化的库都基于案例类。只需使用案例类和模式匹配就可以了。 我需要能够序列化类型类。因此,我需要一种由许多案例类扩展的密封特征的格式。应该是一个相当普遍的场景。 自动 Json.format 似乎无法使用特征,但您可以编写它们:***.com/questions/14145432/…;另外,我偶然发现了这个问题,您可能会感兴趣:***.com/questions/6891393/… @Andy 你介意分享这段代码吗? 【参考方案1】:

于 2015 年 9 月 22 日修订

库play-json-extra 包括play-json-variants 策略,还包括[play-json-extensions] 策略(案例对象的扁平字符串与案例类的对象混合,除非需要,否则无需额外的$variant 或$type)。它还为基于 macramé 的枚举提供序列化器和反序列化器。

上一个答案 现在有一个名为 play-json-variants 的库,它允许您编写:

implicit val format: Format[Foo] = Variants.format[Foo]

这将自动生成相应的格式,它还将通过添加 $variant 属性(相当于 0__ 的 class 属性)来处理以下情况的消歧

sealed trait Foo
case class Bar(x: Int) extends Foo
case class Baz(s: String) extends Foo
case class Bah(s: String) extends Foo

会生成

val bahJson = Json.obj("s" -> "hello", "$variant" -> "Bah") // This is a `Bah`
val bazJson = Json.obj("s" -> "bye", "$variant" -> "Baz") // This is a `Baz`
val barJson = Json.obj("x" -> "42", "$variant" -> "Bar") // And this is a `Bar`

【讨论】:

感谢您的新答案。我不明白为什么作者基本上重写了I did,但是……我们遇到了同样的问题,knownDirectSubclasses 不是由宏系统安全提供的(并确认这将不会很快就会修复) 很可能他不知道这件事......就像我一样:) 你不会知道有一个库可以为缺少的属性创建具有默认值的格式(有关详细信息,请参阅***.com/questions/20616677/…) 您可以将其作为我项目中的功能请求。我不想生成默认值 always,但我可以想象它是一个选项,例如 AutoFormat[Foo](defaults = true) 我不希望一直生成默认值。理想情况下,将有 2 个 sigs :1 个用于默认所有可以默认的值,以及一个 withDefault(key, value) ,第二个将确保键名存在并且提供的默认值具有正确的类型。当我意识到时,我开始编写功能请求,我需要它用于“普通”案例类,而不仅仅是密封特征派生的类......【参考方案2】:

这是Foo伴随对象的手动实现:

implicit val barFmt = Json.format[Bar]
implicit val bazFmt = Json.format[Baz]

object Foo 
  def unapply(foo: Foo): Option[(String, JsValue)] = 
    val (prod: Product, sub) = foo match 
      case b: Bar => (b, Json.toJson(b)(barFmt))
      case b: Baz => (b, Json.toJson(b)(bazFmt))
    
    Some(prod.productPrefix -> sub)
  

  def apply(`class`: String, data: JsValue): Foo = 
    (`class` match 
      case "Bar" => Json.fromJson[Bar](data)(barFmt)
      case "Baz" => Json.fromJson[Baz](data)(bazFmt)
    ).get
  

sealed trait Foo
case class Bar(i: Int  ) extends Foo
case class Baz(f: Float) extends Foo

implicit val fooFmt = Json.format[Foo]   // ça marche!

验证:

val in: Foo = Bar(33)
val js  = Json.toJson(in)
println(Json.prettyPrint(js))

val out = Json.fromJson[Foo](js).getOrElse(sys.error("Oh no!"))
assert(in == out)

或者直接格式定义:

implicit val fooFmt: Format[Foo] = new Format[Foo] 
  def reads(json: JsValue): JsResult[Foo] = json match 
    case JsObject(Seq(("class", JsString(name)), ("data", data))) =>
      name match 
        case "Bar"  => Json.fromJson[Bar](data)(barFmt)
        case "Baz"  => Json.fromJson[Baz](data)(bazFmt)
        case _      => JsError(s"Unknown class '$name'")
      

    case _ => JsError(s"Unexpected JSON value $json")
  

  def writes(foo: Foo): JsValue = 
    val (prod: Product, sub) = foo match 
      case b: Bar => (b, Json.toJson(b)(barFmt))
      case b: Baz => (b, Json.toJson(b)(bazFmt))
    
    JsObject(Seq("class" -> JsString(prod.productPrefix), "data" -> sub))
  


现在理想情况下,我想自动生成 applyunapply 方法。看来我需要使用反射或深入研究宏。

【讨论】:

在我看来,应用/取消应用方法非常危险。如果 json 类名没有用完(Malformed json),get 调用将会结束,你将不会有 json 错误日志记录。 这与我遇到的问题相同......但是答案中的示例代码对我来说失败了......这仍然是首选方法吗? 对我来说,由于与实际不匹配:JsValue[Bar] 和预期:JsValue[Foo],这行 case "Bar" => Json.fromJson[Bar](data)(barFmt) 出现错误【参考方案3】:

0__ 关于直接格式定义的先前答案的一个小修复 - 读取方法不起作用,这是我对它的重构,也变得更加惯用 -

def reads(json: JsValue): JsResult[Foo] = 

  def from(name: String, data: JsObject): JsResult[Foo] = name match 
    case "Bar"  => Json.fromJson[Bar](data)(barFmt)
    case "Baz"  => Json.fromJson[Baz](data)(bazFmt)
    case _ => JsError(s"Unknown class '$name'")
  

  for 
    name <- (json \ "class").validate[String]
    data <- (json \ "data").validate[JsObject]
    result <- from(name, data)
   yield result

【讨论】:

【参考方案4】:

玩 2.7

play-json 支持sealed traits

object Foo
  implicit val format = Json.format[Foo]

玩 2.6

现在可以使用 play-json-derived-codecs 优雅地完成此操作

只需添加这个:

object Foo
    implicit val jsonFormat: OFormat[Foo] = derived.oformat[Foo]()

完整示例请参见此处:ScalaFiddle

【讨论】:

以上是关于带有 Play 2.2 库的密封特征的无噪声 JSON 格式的主要内容,如果未能解决你的问题,请参考以下文章

带有 GIF 的无 JS 响应式图像在 :hover 上显示

如何使用 circe 将密封特征案例对象转换为字符串

用于编码/解码 arity 0 的密封特征实例的 Circe 实例?

将 ADT / 密封特征层次结构编码到 Spark DataSet 列中

火花数据框密封特征类型

抽象类上的 Json.reads(不支持密封特征:没有已知的子类)