Чтобы выделить проблему - предполагая, что ADT:
sealed trait State
case object On extends State
case object Off extends State
Общий вывод circe будет (в настоящее время) производить следующие кодировки:
scala> import io.circe.generic.auto._, io.circe.syntax._
import io.circe.generic.auto._
import io.circe.syntax._
scala> On.asJson.noSpaces
res0: String = {}
scala> (On: State).asJson.noSpaces
res1: String = {"On":{}}
Это связано с тем, что общий механизм деривации построен на LabelledGeneric
Shapeless, который представляет объекты case как пустые HList
s. Это, вероятно, всегда будет поведением по умолчанию, поскольку оно чистое, простое и последовательное, но не всегда то, что вы хотите (как вы заметили, параметры конфигурации, которые скоро появятся, будут поддерживать альтернативы).
Вы можете переопределить это поведение, предоставив свои собственные универсальные экземпляры для объектов case:
import io.circe.Encoder
import shapeless.{ Generic, HNil }
implicit def encodeCaseObject[A <: Product](implicit
gen: Generic.Aux[A, HNil]
): Encoder[A] = Encoder[String].contramap[A](_.productPrefix)
Здесь говорится: «Если общее представление A
является пустым HList
, закодируйте его как его имя как строку JSON». И это работает, как и следовало ожидать, для объектов case, статически типизированных как они сами:
scala> On.asJson.noSpaces
res2: String = "On"
Когда значение статически типизировано как базовый тип, история немного отличается:
scala> (On: State).asJson.noSpaces
res3: String = {"On":"On"}
Мы получаем универсально производный экземпляр для State
, и он учитывает наш вручную определенный универсальный экземпляр для объектов case, но по-прежнему оборачивает их в объект. Если задуматься, в этом есть смысл - ADT может содержать классы case, которые могут быть разумно представлены только как объект JSON, и поэтому подход «объект-оболочка-с-именем-конструктором-ключом» пожалуй, самый разумный поступок.
Однако это не единственное, что мы можем сделать, поскольку мы знаем статически, содержит ли ADT классы case или только объекты case. Сначала нам нужен новый класс типа, который свидетельствует о том, что ADT состоит только из объектов case (обратите внимание, что я предполагаю начать все сначала, но должно быть возможно, чтобы эта работа работала вместе с общим производным):
import shapeless._
import shapeless.labelled.{ FieldType, field }
trait IsEnum[C <: Coproduct] {
def to(c: C): String
def from(s: String): Option[C]
}
object IsEnum {
implicit val cnilIsEnum: IsEnum[CNil] = new IsEnum[CNil] {
def to(c: CNil): String = sys.error("Impossible")
def from(s: String): Option[CNil] = None
}
implicit def cconsIsEnum[K <: Symbol, H <: Product, T <: Coproduct](implicit
witK: Witness.Aux[K],
witH: Witness.Aux[H],
gen: Generic.Aux[H, HNil],
tie: IsEnum[T]
): IsEnum[FieldType[K, H] :+: T] = new IsEnum[FieldType[K, H] :+: T] {
def to(c: FieldType[K, H] :+: T): String = c match {
case Inl(h) => witK.value.name
case Inr(t) => tie.to(t)
}
def from(s: String): Option[FieldType[K, H] :+: T] =
if (s == witK.value.name) Some(Inl(field[K](witH.value)))
else tie.from(s).map(Inr(_))
}
}
А затем наши общие Encoder
экземпляры:
import io.circe.Encoder
implicit def encodeEnum[A, C <: Coproduct](implicit
gen: LabelledGeneric.Aux[A, C],
rie: IsEnum[C]
): Encoder[A] = Encoder[String].contramap[A](a => rie.to(gen.to(a)))
Можно также пойти дальше и написать декодер.
import cats.data.Xor, io.circe.Decoder
implicit def decodeEnum[A, C <: Coproduct](implicit
gen: LabelledGeneric.Aux[A, C],
rie: IsEnum[C]
): Decoder[A] = Decoder[String].emap { s =>
Xor.fromOption(rie.from(s).map(gen.from), "enum")
}
А потом:
scala> import io.circe.jawn.decode
import io.circe.jawn.decode
scala> import io.circe.syntax._
import io.circe.syntax._
scala> (On: State).asJson.noSpaces
res0: String = "On"
scala> (Off: State).asJson.noSpaces
res1: String = "Off"
scala> decode[State](""""On"""")
res2: cats.data.Xor[io.circe.Error,State] = Right(On)
scala> decode[State](""""Off"""")
res3: cats.data.Xor[io.circe.Error,State] = Right(Off)
Что мы и хотели.
person
Travis Brown
schedule
03.05.2016