Scala Circe с дженериками

Я пытаюсь использовать scala json-библиотеку Circe, обернув ее простой чертой, чтобы обеспечить преобразование в / из json, для чего у меня есть следующее:

import io.circe.generic.auto._
import io.circe.parser._
import io.circe.syntax._

trait JsonConverter {
  def toJson[T](t : T) : String
  def fromJson[T](s: String) : T
}

case class CirceJsonConverter() extends JsonConverter{
  override def toJson[T](t: T): String = t.asJson.noSpaces
  override def fromJson[T](s: String): T = decode[T](s).getOrElse(null).asInstanceOf[T]
}

Цель этого - просто иметь возможность вызывать JsonConverter с любым объектом и преобразовывать его в / из json как такового jsonConverter.toJson(0) must equalTo("0"), однако, когда я пытаюсь его скомпилировать, я получаю следующее:

[error] could not find implicit value for parameter encoder: io.circe.Encoder[T]
[error]   override def toJson[T](t: T): String = t.asJson.noSpaces
[error]                                            ^
[error] could not find implicit value for parameter decoder: io.circe.Decoder[T]
[error]   override def fromJson[T](s: String): T = decode[T](s).getOrElse(null).asInstanceOf[T]
[error]                                                     ^
[error] two errors found

У меня, конечно, может быть класс, от которого наследуется все, что я собираюсь передать через конвертер, но у меня сложилось впечатление, что circe может автоматически генерировать кодеры / декодеры?


person RichyHBM    schedule 22.09.2016    source источник


Ответы (2)


То, что вы хотите, не сработает, если вы не реализуете стратегию превращения любого объекта в Json ... что кажется маловероятным. Circe (и многие другие библиотеки) вместо этого предпочитают использовать общий шаблон, называемый классами типов, чтобы было удобно определять, как вы хотите что-то делать, в данном случае _1 _ / _ 2_, для определенного типа.

Я рекомендую изучить классы типов, если вы с ними не знакомы. А затем взгляните на документацию Circe, чтобы узнать, как конкретно можно реализовать кодеры / декодеры.

person Idan Waisman    schedule 22.09.2016
comment
Для записи, импорт io.circe.generic.auto._ - удобный способ создания экземпляров, но он не создает экземпляры для чего-либо и для всего волшебным образом. Он делает их только для классов case, определенных в файле с этим импортом. - person Idan Waisman; 23.09.2016
comment
одна хорошая альтернатива для import io.circe.generic.auto._, тогда это import io.circe.generic.semiauto._ (что я предпочитаю), а затем явно один раз по типу: lazy implicit val e01: Encoder[MyType] = deriveEncoder и lazy implicit val d01: Decoder[MyType] = deriveDecoder - person Hartmut P.; 17.03.2021

После ответа Идана Вайсмана и ответа C4stor в моем дубликате question Я использовал шаблон Type Classes. Для краткости я привожу пример кода только для декодирования json. Точно так же можно реализовать кодирование.

Во-первых, давайте определим трейт, который будет использоваться для внедрения зависимости декодера json:

trait JsonDecoder[T] {
  def apply(s: String): Option[T]
}

Затем мы определяем объект, который создает экземпляр, реализующий эту черту:

import io.circe.Decoder
import io.circe.parser.decode

object CirceDecoderProvider {
  def apply[T: Decoder]: JsonDecoder[T] =
    new JsonDecoder[T] {
      def apply(s: String) =
        decode[T](s).fold(_ => None, s => Some(s))
    }
}

Как вы можете заметить, apply требует, чтобы неявный io.circe.Decoder[T] находился в области видимости при вызове.

Затем мы копируем io.circe.generic.auto содержимое объекта и создаем черту (я сделал PR, чтобы эта черта доступно как io.circe.generic.Auto):

import io.circe.export.Exported
import io.circe.generic.decoding.DerivedDecoder
import io.circe.generic.encoding.DerivedObjectEncoder
import io.circe.{ Decoder, ObjectEncoder }
import io.circe.generic.util.macros.ExportMacros
import scala.language.experimental.macros

trait Auto {
  implicit def exportDecoder[A]: Exported[Decoder[A]] = macro ExportMacros.exportDecoder[DerivedDecoder, A]
  implicit def exportEncoder[A]: Exported[ObjectEncoder[A]] = macro ExportMacros.exportEncoder[DerivedObjectEncoder, A]
}

Затем в пакете (например, com.example.app.json), который часто использует декодирование json, мы создаем объект пакета, если он не существует, и заставляем его расширять черту Auto и обеспечивать неявный возврат JsonDecoder[T] для данного типа T:

package com.example.app

import io.circe.Decoder

package object json extends Auto {
    implicit def decoder[T: Decoder]: JsonDecoder[T] = CirceDecoderProvider[T]
}

Теперь:

  • все исходные файлы в com.example.app.json имеют Auto значение
  • вы можете получить JsonDecoder[T] для любого типа T, который имеет io.circe.Decoder[T] или для которого он может быть сгенерирован с Auto имплицитами
  • вам не нужно импортировать io.circe.generic.auto._ в каждый файл
  • вы можете переключаться между json-библиотеками, изменяя только com.example.app.json содержимое объекта пакета.

Например, вы можете переключиться на json4s (хотя я сделал наоборот и переключился на circe с json4s). Провайдер внедрения для JsonDecoder[T]:

import org.json4s.Formats
import org.json4s.native.JsonMethods._

import scala.util.Try

case class Json4SDecoderProvider(formats: Formats) {
  def apply[T: Manifest]: JsonDecoder[T] =
    new JsonDecoder[T] {
      def apply(s: String) = {
        implicit val f = formats
        Try(parse(s).extract[T]).toOption
      }
    }
}

И измените содержимое объекта пакета com.example.app.json на:

package com.example.app

import org.json4s.DefaultFormats

package object json {
    implicit def decoder[T: Manifest]: JsonDecoder[T] = Json4SDecoderProvider(DefaultFormats)[T]
}

С помощью шаблона Type Classes вы получаете инъекцию зависимостей во время компиляции. Это дает вам меньшую гибкость, чем внедрение зависимостей во время выполнения, но я сомневаюсь, что вам нужно переключать парсеры json во время выполнения.

person mixel    schedule 12.12.2016