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:
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:
[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?
Following Idan Waisman answer and C4stor answer in my duplicate question I used the Type Classes pattern. For brevity I provide sample code only for decoding 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:
Next we define object that creates instance implementing this trait:
As you can notice
apply
requires implicitio.circe.Decoder[T]
to be in scope when it called.Then we copy
io.circe.generic.auto
object content and create a trait (I made PR to have this trait available asio.circe.generic.Auto
):Next in the package (e.g.
com.example.app.json
) that uses json decoding a lot we create package object if does not exist and make it extendAuto
trait and provide implicit returningJsonDecoder[T]
for given typeT
:Now:
com.example.app.json
hasAuto
implicits in scopeJsonDecoder[T]
for any typeT
that hasio.circe.Decoder[T]
or for which it can be generated withAuto
implicitsio.circe.generic.auto._
in every filecom.example.app.json
package object content.For example you can switch to json4s (though I did the opposite and switched to circe from json4s). Implement provider for
JsonDecoder[T]
:And change
com.example.app.json
package object content to:With Type Classes pattern you get compile-time dependency injection. That gives you less flexibility than runtime dependency injection but I doubt that you need to switch json parsers in runtime.
What you want is not going to work unless you can implement a strategy for turning any object into Json... which seems unlikely. 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.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.