Кодирование классов дел ADT с помощью дискриминатора, даже если оно введено в качестве класса дел
Предположим, у меня есть ADT в Scala:
sealed trait Base
case class Foo(i: Int) extends Base
case class Baz(x: String) extends Base
Я хочу закодировать значения этого типа в JSON, который выглядит следующим образом:
{ "Foo": { "i": 10000 }}
{ "Baz": { "x": "abc" }}
К счастью, это именно то, что обеспечивает общий вывод кодировочной системы!
scala> import io.circe.generic.auto._, io.circe.syntax._
import io.circe.generic.auto._
import io.circe.syntax._
scala> val foo: Base = Foo(10000)
foo: Base = Foo(10000)
scala> val baz: Base = Baz("abc")
baz: Base = Baz(abc)
scala> foo.asJson.noSpaces
res0: String = {"Foo":{"i":10000}}
scala> baz.asJson.noSpaces
res1: String = {"Baz":{"x":"abc"}}
Проблема заключается в том, что использование кодировщика зависит от статического типа выражения, которое мы кодируем. Это означает, что если мы пытаемся декодировать один из классов case напрямую, мы теряем дискриминатор:
scala> Foo(10000).asJson.noSpaces
res2: String = {"i":10000}
scala> Baz("abc").asJson.noSpaces
res3: String = {"x":"abc"}
... но я хочу Base
кодирование, даже когда статический тип Foo
, Я знаю, что могу определить явные экземпляры для всех классов case, но в некоторых случаях их может быть много, и я не хочу перечислять их.
(Обратите внимание, что это вопрос, который поднимался несколько раз - например, здесь.)
1 ответ
Это можно сделать довольно просто, определив экземпляр для подтипов базового типа, который просто делегирует Base
декодер:
import cats.syntax.contravariant._
import io.circe.ObjectEncoder, io.circe.generic.semiauto.deriveEncoder
sealed trait Base
case class Foo(i: Int) extends Base
case class Baz(x: String) extends Base
object Base {
implicit val encodeBase: ObjectEncoder[Base] = deriveEncoder
}
object BaseEncoders {
implicit def encodeBaseSubtype[A <: Base]: ObjectEncoder[A] = Base.encodeBase.narrow
}
Работает как положено:
scala> import BaseEncoders._
import BaseEncoders._
scala> import io.circe.syntax._
import io.circe.syntax._
scala> Foo(10000).asJson.noSpaces
res0: String = {"Foo":{"i":10000}}
scala> (Foo(10000): Base).asJson.noSpaces
res1: String = {"Foo":{"i":10000}}
к несчастью encodeBaseSubtype
не может быть определено в Base
сопутствующий объект, с тех пор он будет поднят deriveEncoder
макрос, приводящий к циклическому определению (и переполнению стека и т. д.). Я думаю, что в какой-то момент я нашел какой-то ужасный обходной путь для этой проблемы - я постараюсь найти его и опубликовать в качестве другого ответа, если я это сделаю.