Circe asJson не кодирует свойства из абстрактного базового класса

Предположим, у меня есть следующий абстрактный базовый класс:

package Models

import reactivemongo.bson.BSONObjectID


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

Который расширен следующим конкретным классом case:

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"? 

Как видите, недвижимость _id: String унаследовано от RecordObjectпропал, отсутствует. Я ожидал, что встроенный кодировщик должен нормально работать в этом случае. Мне действительно нужно создавать свою собственную?

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 неизменяемый и невозможно установить извне через конструктор, поэтому его также сложно реализовать каким-либо другим способом.

Другие вопросы по тегам