Circe

Para serializar/deserializar a Json instancias en Scala, más si usamos Cats, una opción bastante mayoritaria es usar la librería Circe.

Voy a usar un caso de uso concreto e ir implementando (o usando derivación automática) para ver el porqué de las decisiones tomadas.

da error al tener Bar una instancia de Foo

type Point = (Float, Float)

given Encoder[Point] = deriveEncoder[Point]
given Decoder[Point] = deriveDecoder[Point]

Pareja de números. Circe puede derivar directamente. Si queremos hacerlo explícitamente podemos usar deriveEncoder y deriveDecoder.

case class Polygon(coordinates: Vector[Vector[Point]])
  extends CoordinateContainer[Vector[Vector[Point]]](coordinates = coordinates, TYPE = "Polygon")

given Encoder[Polygon] = addType(deriveEncoder[Polygon], "Polygon")

Para poder enviar un parámetro de nombre type, al ser una palabra reservada del lenguaje, tengo que crear otro atributo de nombre diference (TYPE) y luego mapear o insertar. De las varias formas que he probado, usar una función addType es la que mejor resultado me ha dado.

def addType[T <: GeoJson](encoder: Encoder[T], t: String): Encoder[T] =
  encoder.mapJson { _.mapObject(_.add("type", t.asJson)) }

Hay funcionalidades extras en el paquete circe-generic-extra que parece que deberían funcionar, pero no está actualizado a Scala3.

Es un tipo unión. Para codificar necesitamos tener disponibles las instancias de todos los tipos que forman la unión (ya los hemos definido anteriormente). Para decodificar hacemos una lista de los decodificadores y los aplicamos hasta que uno funciona.

Podemos hacer algo similar a la codificación, crear una u otra instancia mediante pattern matching. Posiblemente tenga que ir a este método tarde o temprano.

deriveEncoder usa todos los codificadores que el compilador tiene disponibles para los miembros de Feature. Por esta razón, no es necesario crear un decodificador, porque de forma automática se deriva (lo mismo que hacer given Decoder[Feature] = deriveDecoder[Feature]).

El método .asJson precisamente usa dichas instancias por lo que si el compilador no encuentra alguna en el contexto obviamente nos da error.

final def asJson(implicit encoder: Encoder[A]): Json = encoder(value)