У меня двойная кодировка

Этот вопрос поднимался несколько раз в последнее время, поэтому я задаю его здесь. Предположим, у меня есть несколько классов дел, как это:

import io.circe._, io.circe.generic.semiauto._

object model {
  case class A(a: String)
  case class B(a: String, i: Int)
  case class C(i: Int, b: Boolean)

  implicit val encodeA: Encoder[A] = deriveEncoder
  implicit val encodeB: Encoder[B] = deriveEncoder
  implicit val encodeC: Encoder[C] = deriveEncoder
  implicit val decodeA: Decoder[A] = deriveDecoder
  implicit val decodeB: Decoder[B] = deriveDecoder
  implicit val decodeC: Decoder[C] = deriveDecoder
}

И я хочу закодировать значение, которое может быть любым из них, как JSON, используя circe и Shapeless.

import io.circe.shapes._, io.circe.syntax._
import shapeless._

import model._

type ABC = A :+: B :+: C :+: CNil

val c: ABC = Coproduct[ABC](C(123, false))

Это выглядит хорошо на первый взгляд:

scala> c.asJson
res0: io.circe.Json =
{
  "i" : 123,
  "b" : false
}

Но проблема в том, что я никогда не смогу декодировать копродукт, содержащий B элемент, поскольку любой действительный документ JSON, который может быть декодирован как B также может быть расшифрован как Aи декодеры копроизведения, обеспеченные кольцевыми формами, пробуют элементы в порядке, в котором они появляются в копроизведении.

scala> val b: ABC = Coproduct[ABC](B("xyz", 123))
b: ABC = Inr(Inl(B(xyz,123)))

scala> val json = b.asJson
json: io.circe.Json =
{
  "a" : "xyz",
  "i" : 123
}

scala> io.circe.jawn.decode[ABC](json.noSpaces)
res1: Either[io.circe.Error,ABC] = Right(Inl(A(xyz)))

Как я могу устранить неоднозначность элементов моего копроизведения в моей кодировке?

1 ответ

Решение

Модуль circe-shape кодирует обычные hlists и копродукты без меток, как показано выше: копроизведение как голое JSON-представление элемента, а hlist заканчивается как просто JSON-массив (такой же, как кодировка кортежа по умолчанию):

scala> ("xyz" :: List(1, 2, 3) :: false :: HNil).asJson.noSpaces
res2: String = ["xyz",[1,2,3],false]

В случае hlists нет опасности двусмысленности из-за перекрывающихся имен, но для сопутствующих товаров есть. Однако в обоих случаях вы можете добавить метки в представление JSON, используя механизм маркировки Shapeless, который маркирует значения символом уровня типа.

Список с метками называется "записью", а копроизведение с метками - "объединением". Shapeless предоставляет специальный синтаксис и операции для обоих из них, а перистые формы обрабатывают как отличные от немаркированных списков, так и копроизведений. Например (при условии определения и импорта выше):

scala> import shapeless.union._, shapeless.syntax.singleton._
import shapeless.union._
import shapeless.syntax.singleton._

scala> type ABCL = Union.`'A -> A, 'B -> B, 'C -> C`.T
defined type alias ABCL

scala> val bL: ABCL = Coproduct[ABCL]('B ->> B("xyz", 123))
bL: ABCL = Inr(Inl(B(xyz,123)))

scala> val jsonL = bL.asJson
jsonL: io.circe.Json =
{
  "B" : {
    "a" : "xyz",
    "i" : 123
  }
}

scala> io.circe.jawn.decode[ABCL](jsonL.noSpaces)
res3: Either[io.circe.Error,ABCL] = Right(Inr(Inl(B(xyz,123))))

Кодировка для записей аналогичным образом включает имена членов:

scala> ('a ->> "xyz" :: 'b ->> List(1) :: 'c ->> false :: HNil).asJson.noSpaces
res4: String = {"c":false,"b":[1],"a":"xyz"}

В общем, если вы хотите, чтобы ваш hlist и кодирование копродукции включали метки (и выглядели как кодировки, которые вы получили бы от circe-generic для классов прецедентов и иерархий запечатанных признаков), вы можете использовать записи или сопутствующие продукты, чтобы сказать circe-shape сделать это. с минимальным количеством дополнительных синтаксических и временных накладных расходов.

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