【问题标题】:Scala Circe with genericsScala Circe 与泛型
【发布时间】:2023-03-12 14:10:02
【问题描述】:

我正在尝试使用 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 可以自动生成编码器/解码器?

【问题讨论】:

    标签: json scala circe


    【解决方案1】:

    按照 Idan Waisman 的回答和 C4stor answer 在我的重复 question 中,我使用了类型类模式。为简洁起见,我仅提供用于解码 json 的示例代码。编码的实现方式完全相同。

    首先,让我们定义将用于注入 json 解码器依赖项的 trait:

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

    接下来我们定义对象来创建实现这个 trait 的实例:

    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]
    }
    

    接下来在使用json解码的包(例如com.example.app.json)中,如果不存在,我们创建包对象并使其扩展Auto特征并为给定类型T提供隐式返回JsonDecoder[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
    • 对于任何具有io.circe.Decoder[T] 或可以使用Auto 隐式生成的类型T,您可以获得JsonDecoder[T]
    • 您不需要在每个文件中都导入io.circe.generic.auto._
    • 只需更改com.example.app.json包对象内容即可在json库之间切换。

    例如,您可以切换到json4s(尽管我做了相反的操作并从 json4s 切换到 circe)。为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]
    }
    

    使用类型类模式,您可以获得编译时依赖注入。与运行时依赖注入相比,这为您提供了更少的灵活性,但我怀疑您是否需要在运行时切换 json 解析器。

    【讨论】:

      【解决方案2】:

      除非您可以实施将任何对象转换为 Json 的策略,否则您想要的不会起作用……这似乎不太可能。 Circe(和许多其他库)选择使用一种称为 Type Classes 的通用模式来方便地定义您想要如何做某事,在本例中为Encoder/Decoder,用于特定类型 em>。

      如果您不熟悉类型类,我建议您研究它们。然后查看 Circe 文档,了解如何具体实现编码器/解码器。

      【讨论】:

      • 作为记录,io.circe.generic.auto._ 的导入是一种生成实例的便捷方式,但它并不能神奇地为任何事物生成实例。它只为使用该导入的文件中定义的案例类制作它们。
      • import io.circe.generic.auto._ 的一个不错的替代方案是 import io.circe.generic.semiauto._(我更喜欢),然后按类型显式一次:lazy implicit val e01: Encoder[MyType] = deriveEncoderlazy implicit val d01: Decoder[MyType] = deriveDecoder
      猜你喜欢
      • 2021-02-21
      • 2017-08-21
      • 1970-01-01
      • 2017-05-02
      • 2017-06-23
      • 2015-04-09
      • 2010-11-03
      • 2019-03-15
      • 1970-01-01
      相关资源
      最近更新 更多