简体   繁体   English

Scala Circe与泛型

[英]Scala Circe with generics

I am trying to use the scala json library Circe, wrapping it in a simple trait to provide conversion to/from json for which I have the following: 我正在尝试使用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]
}

The aim of this is to simply be able to call JsonConverter with any object and have it convert it to/from json as such jsonConverter.toJson(0) must equalTo("0") , however when I try to compile it I get the following: 这样做的目的是简单地能够使用任何对象调用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

I can of course have a class that everything I intend to put through the converter inherit from, but I had the impression that circe could auto generate the encoders/decoders? 我当然可以有一个类,我打算通过转换器继承的所有东西,但我有一个印象,circe可以自动生成编码器/解码器?

What you want is not going to work unless you can implement a strategy for turning any object into Json... which seems unlikely. 除非你可以实现将任何对象转换为Json的策略,否则你想要的东西是行不通的......这似乎不太可能。 Circe (and many other libs) instead choose to use a common pattern called Type Classes to make it convenient to define how you want to do something, in this case Encoder / Decoder , for a specific type . Circe(以及许多其他库)改为选择使用名为Type Classes的通用模式,以便于定义您想要执行某些操作的方式,在本例中为特定类型的 Encoder / Decoder

I recommend researching Type Classes if you are unfamiliar with them. 如果您不熟悉它,我建议研究类型类。 And then take a look at the Circe docs to see how you can implement Encoders/Decoders specifically. 然后查看Circe文档,了解如何专门实现编码器/解码器。

Following Idan Waisman answer and C4stor answer in my duplicate question I used the Type Classes pattern. 继Idan Waisman回答和C4stor在我的重复问题中 回答后 ,我使用了Type Classes模式。 For brevity I provide sample code only for decoding json. 为简洁起见,我提供的示例代码仅用于解码json。 Encoding can be implemented in exactly the same way. 编码可以以完全相同的方式实现。

First, let's define the trait that will be used to inject json decoder dependency: 首先,让我们定义将用于注入json解码器依赖的特征:

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

Next we define object that creates instance implementing this 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))
    }
}

As you can notice apply requires implicit io.circe.Decoder[T] to be in scope when it called. 正如你可以看到apply要求隐式io.circe.Decoder[T]在范围上,当它被调用。

Then we copy io.circe.generic.auto object content and create a trait (I made PR to have this trait available as io.circe.generic.Auto ): 然后我们复制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]
}

Next in the package (eg com.example.app.json ) that uses json decoding a lot we create package object if does not exist and make it extend Auto trait and provide implicit returning JsonDecoder[T] for given type T : 接下来在包中使用json解码的包(例如com.example.app.json ),如果不存在则创建包对象并使其扩展Auto trait并为给定类型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]
}

Now: 现在:

  • all source files in com.example.app.json has Auto implicits in scope com.example.app.json所有源文件都在范围内具有Auto implicits
  • you can get JsonDecoder[T] for any type T that has io.circe.Decoder[T] or for which it can be generated with Auto implicits 您可以为任何具有io.circe.Decoder[T]类型T获取JsonDecoder[T] ,或者可以使用Auto implicits生成它
  • you do not need to import io.circe.generic.auto._ in every file 您不需要在每个文件中导入io.circe.generic.auto._
  • you can switch between json libraries by only changing com.example.app.json package object content. 您只需更改com.example.app.json包对象内容即可在json库之间切换。

For example you can switch to json4s (though I did the opposite and switched to circe from json4s). 例如,您可以切换到json4s (虽然我做了相反的事情并从json4s切换到circe)。 Implement provider for JsonDecoder[T] : JsonDecoder[T]实现提供程序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
      }
    }
}

And change com.example.app.json package object content to: 并将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]
}

With Type Classes pattern you get compile-time dependency injection. 使用Type Classes模式,您可以获得编译时依赖项注入。 That gives you less flexibility than runtime dependency injection but I doubt that you need to switch json parsers in runtime. 这比运行时依赖注入的灵活性低,但我怀疑你需要在运行时切换json解析器。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM