问题
I am learning Circe and Scala for a project at work. To explain my issue, start with the following example:
import io.circe.syntax._
object TestDrive extends App {
val labels = Seq("Banana", "Banano", "Grapefruit")
println(labels.asJson)
}
Ok so this outputs:
["Banana","Banano","Grapefruit"]
This is good.
Now I want to make my code a bit more general. I want to write a function that takes in a Sequence, whose elements can be of type AnyVal.
Here is my attempt:
import io.circe.syntax._
import io.circe.Json
object TestDrive extends App {
def f1[T](lst: Seq[T]): Json = {
lst.asJson
}
val labels = Seq("Banana", "Banano", "Grapefruit")
println(f1(labels))
}
This fails because:
could not find implicit value for parameter encoder: io.circe.Encoder[Seq[T]]
Ok so I need to make an implicit value for the encoder because the type T is too general. Here is my second attempt using scala ClassTags:
import io.circe.syntax._
import io.circe.Json
import scala.reflect.ClassTag
object TestDrive extends App {
def f1[T <: AnyVal](lst: Seq[T])(implicit ev: ClassTag[T]): Json = {
lst.asJson
}
val labels = Seq("Banana", "Banano", "Grapefruit")
println(f1(labels))
}
This fails with:
type mismatch;
found : Seq[String]
required: Seq[T]
How do I go about solving this? I read through the Circe docs but I cannot understand how to handle this type of example.
If someone could kindly explain, with a bit of explanation as to how they resolve something like this, it would be much appreciated. I should add I am new to Scala so any explanation would be useful which explains the theory too.
Thanks!
回答1:
Circe is built on the type class pattern, and its Encoder
is one of the type classes it provides. The key idea is that instead of using something like runtime reflection to figure out how to encode some arbitrary value, you require (and provide) a type class instance for any particular type you need to encode.
If you're working with a concrete type, the compiler will tell you whether you have a type class instance in scope or not. List("a", "b").asJson
will compile, for example, while List(1, "a").asJson
(where the inferred type is List[Any]
) won't. This is because Circe provides an implicit Encoder[List[String]]
, but not an implicit Encoder[List[Any]]
.
If you're working with a generic type, you need a type class constraint. In your case that'd look like this:
def f1[T: Encoder](lst: Seq[T]): Json = {
lst.asJson
}
Which is syntactic sugar for something similar to thi:
def f1[T](lst: Seq[T])(implicit encodeT: Encoder[T]): Json = {
lst.asJson
}
You'll need to include this constraint up through the call chain.
As a footnote, in reference to the shapeless
tag, it's worth noting that the type class pattern is separate from the idea of generic derivation, which is often done in Scala with Shapeless. When you write import io.circe.generic.auto._
, that's one way of putting Encoder
and Decoder
type class instances into scope for case classes. But you never need generic derivation—it's just one convenient way of defining type class instances using compile-time reflection. All of the information above is exactly the same whether you use generic derivation or manually-written instances.
回答2:
import io.circe._
import io.circe.syntax._
import io.circe.Json
object TestDrive extends App {
def f1[T](lst: Seq[T])(implicit encoder: Encoder[T]): Json = {
lst.asJson
}
val labels = Seq("Banana", "Banano", "Grapefruit")
println(f1(labels))
val intLabels = Seq(1,2,3)
println(f1(intLabels))
}
You basically need to provide encoders for achieving it. By default, circe-core will take care of handling json creation for scala general types (like collections, options, and normal types). In case if you need for specific case class you can still use circe-generic
.
Circe achieves automatic derivation by using Shapeless. https://circe.github.io/circe/codecs/auto-derivation.html
来源:https://stackoverflow.com/questions/62304787/how-to-convert-seq-to-json-using-circe-inside-a-function-keep-getting-implici