Кодирование классов дел ADT с помощью дискриминатора, даже если оно введено в качестве класса дел - PullRequest
0 голосов
/ 17 октября 2018

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

(Обратите внимание, что этовопрос, который возник несколько раз - например, здесь .)

1 Ответ

0 голосов
/ 17 октября 2018

Это можно сделать довольно просто, определив экземпляр для подтипов базового типа, который просто делегирует декодеру 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, что приведет к циклическому определению (и переполнению стека и т. Д.).Я думаю, что в какой-то момент я нашел какой-то ужасный обходной путь для этой проблемы - я постараюсь найти его и опубликовать в качестве другого ответа, если я это сделаю.

...