【问题标题】:Circe asJson not encoding properties from abstract base classCirce asJson 不编码抽象基类的属性
【发布时间】:2020-08-09 07:56:19
【问题描述】:

假设我有以下抽象基类:

package Models

import reactivemongo.bson.BSONObjectID


abstract class RecordObject {
  val _id: String = BSONObjectID.generate().stringify
}

由以下具体案例类扩展:

package Models

case class PersonRecord(name: String) extends RecordObject

然后我尝试使用如下代码获取 JSON 字符串:

import io.circe.syntax._
import io.circe.generic.auto._
import org.http4s.circe._
// ...

val person = new PersonRecord(name = "Bob")
println(person._id, person.name) // prints some UUID and "Bob"
println(person.asJso) // {"name": "Bob"} -- what happened to "_id"? 

如您所见,继承自RecordObject 的属性_id: String 丢失了。我希望内置编码器应该可以很好地用于这个用例。我真的需要自己构建吗?

【问题讨论】:

    标签: json scala circe http4s-circe


    【解决方案1】:

    让我们看看编码器生成过程中发生了什么。 Circe 使用 shapeless 来派生其编解码器,因此足以检查 shapeless 解析为什么来回答您的问题。所以在菊石中:

    @ abstract class RecordObject {
        val _id: String = java.util.UUID.randomUUID.toString
      }
    defined class RecordObject
    
    @ case class PersonRecord(name: String) extends RecordObject
    defined class PersonRecord
    
    @  import $ivy.`com.chuusai::shapeless:2.3.3`, shapeless._
    import $ivy.$                             , shapeless._
    
    @ Generic[PersonRecord]
    res3: Generic[PersonRecord]{type Repr = String :: shapeless.HNil} = ammonite.$sess.cmd3$anon$macro$2$1@1123d461
    

    好的,那就是String :: HNil。很公平 - shapeless 所做的是提取构造函数中可用的所有字段转换一种方式,如果转换另一种方式,则通过构造函数将所有字段放回。

    基本上所有类型类派生都以这种方式工作,因此您应该可以将_id 作为构造函数传递:

    abstract class RecordObject {
        val _id: String
    }
    
    case class PersonRecord(
      name: String,
      _id: String = BSONObjectID.generate().stringify
    ) extends RecordObject
    

    这将有助于类型类派生完成它的工作。如果您无法更改 PersonRecord 的外观......那么是的,您必须编写自己的编解码器。虽然我怀疑这会很容易,因为您使 _id 不可变并且无法通过构造函数从外部设置,因此使用任何其他方式也很难实现。

    【讨论】:

    • 感谢您的出色回答!我有一种感觉,这与我对 Scala 继承的薄弱理解有关。这确实有效,但现在继承自 RecordObject 的类负责实现 ID 生成。我希望这是自动的,并且一旦实例化类,ID 就不可更改。有没有办法做到这一点?
    • 必须定义一个允许你覆盖_id的构造函数——你怎么能用特定的ID重新创建一个实例?但是,您可以在RecordObject 中创建protected 方法generateID 并重载构造函数以使用它:def this(name: String) = this(name, generateID)。但是,您永远无法确定扩展您的界面的用户会使用它。约束是没有意义的,因为您仍然需要从外部世界传递任意的String 才能重新创建一个值。该字符串在到达构造函数之前的任何时候都可能被破坏。
    • 我要做的就是创建一个单独的类型来表示 BSONObjectID。这种类型将有两种创建自身的公共方法,一种将String 解析为Either[Error, BSONObjectID] 之类的东西,另一种用于创建新ID 的方法。然后我会为该 ID 手动编写编解码器,以确保如果 ID 被破坏 - 它会导致 Circe 解析失败。在这一点上,我不必担心下游会以错误的方式使用 ID,因为他们不可能这样做。
    • 明白了。非常感谢您的帮助、您的时间和您的快速回复。令我惊喜的是,SO 上的 Scala 社区似乎比一年前更加活跃。
    • 我想查找以下内容:类型类派生、使非法状态不可替代、ADT 以及类型和隐式一般不会受到伤害。
    猜你喜欢
    • 1970-01-01
    • 2018-10-22
    • 2019-02-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-11
    • 2013-04-18
    • 2020-08-29
    • 1970-01-01
    相关资源
    最近更新 更多