Circe asJson 没有从抽象基础 class 编码属性

Circe asJson not encoding properties from abstract base class

假设我有以下抽象基 class:

package Models

import reactivemongo.bson.BSONObjectID


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

由以下具体案例扩展class:

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。我希望内置编码器在这个用例中运行良好。我真的需要自己构建吗?

让我们看看编码器生成过程中发生了什么。 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@1123d461

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

基本上所有类型class 派生都是这样工作的,所以你应该可以将 _id 作为构造函数传递:

abstract class RecordObject {
    val _id: String
}

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

这将有助于类型 class 派生工作。如果你不能改变 PersonRecord 的样子……那么是的,你必须编写自己的编解码器。尽管我怀疑这会很容易,因为您使 _id 不可变并且不可能通过构造函数从外部设置,因此也很难使用任何其他方式实现。