New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JsonCodec for sealed traits requires an explicit object definition #251
Comments
The work around doesn't seem to work (both expanded or Could not find Lazy implicit value of type io.circe.generic.encoding.DerivedObjectEncoder[A] and I only see problem with |
@ngbinh Any chance you could share a reproduction? At least in the simple case above the workaround works just fine. |
I will work on a reproduction. It could be because my |
it's funny though sealed trait A
case class B(b: String) extends A
case class C(c: Int) extends A
object A {
implicit val encodeA: Encoder[A] = io.circe.generic.semiauto.deriveEncoder[A]
implicit val decodeA: Decoder[A] = io.circe.generic.semiauto.deriveDecoder[A]
} gives me /path-to-files.scala:18: could not find Lazy implicit value of type io.circe.generic.encoding.DerivedObjectEncoder[package.A]
[error] implicit val encodeA: Encoder[A] = io.circe.generic.semiauto.deriveEncoder[A] when I copy and paste the code in one of the class I have problem with. |
That's extremely weird. I'd love to see a minimization that triggers this—thanks for working on it. |
So, I try to work on a small reproduce and this is what i found: https://github.com/ngbinh/scala-js-example-app/blob/circe-251/src/main/scala/example/Model.scala import io.circe.generic.JsonCodec
import io.circe.syntax._
@JsonCodec sealed trait A
case class B(b: String) extends A
case class C(c: Int) extends A
object A
object Test {
B("abc").asJson.noSpaces // -> error
} Without could not find implicit value for parameter encoder: io.circe.Encoder[example.B]
[error] B("abc").asJson.noSpaces
[error] ^
[error] one error found |
And of course if I ask for object Test {
implicitly[Encoder[B]
} then could not find implicit value for parameter e: io.circe.Encoder[example.B]
[error] implicitly[Encoder[B]]
[error] ^
[error] one error found |
and we are on circe |
Oh, now I think I see the problem. When you ask scala> B("abc").asJson.noSpaces
<console>:24: error: could not find implicit value for parameter encoder: io.circe.Encoder[B]
B("abc").asJson.noSpaces
^
scala> (B("abc"): A).asJson.noSpaces
res1: String = {"B":{"b":"abc"}} So you could either upcast or explicitly derive instances for all of the children—which you can do with @JsonCodec sealed trait A
@JsonCodec case class B(b: String) extends A
@JsonCodec case class C(c: Int) extends A
object A And then: scala> B("abc").asJson.noSpaces
res2: String = {"b":"abc"} Does that make sense? |
ok, makes sense now. The trouble now is when constructing a model If we add |
The exact static type always determines which instance will be used, and circe-generic will always give different instances for If you wanted instances for import io.circe._, io.circe.generic.JsonCodec, io.circe.syntax._
sealed trait A
@JsonCodec case class B(b: String) extends A
@JsonCodec case class C(c: Int) extends A
object A {
implicit val decodeA: Decoder[A] = Decoder[B].map[A](identity).or(Decoder[C].map[A](identity))
implicit val encodeA: Encoder[A] = Encoder.instance {
case b @ B(_) => b.asJson
case c @ C(_) => c.asJson
}
} And then: scala> (B("abc"): A).asJson.noSpaces
res0: String = {"b":"abc"}
scala> B("abc").asJson.noSpaces
res1: String = {"b":"abc"} This isn't the default because in some cases it can lead to ambiguity in decoding, but if your case class member names don't overlap it can be a reasonable thing to do. |
thanks! Got it now. |
Is this issue blocking Circe 0.5.0? |
I get the same error when trying to convert a simple case class to Json
And if I try to use the annotation the error is:
|
Needing to care about the order of where the |
JSonCodec fails when the last case class has nested case classes in it.
|
Is it supposed to work if I nest the case classes inside the sealed trait's companion? @JsonCodec
sealed trait A
object A {
case class A1(x: String) extends A
case class A2(y: Int) extends A
} |
I'm trying to use Relevant code snippet: @JsonCodec
sealed trait Command {
val id: CommandId
val body: String
type M <: CommandMetaData
val meta: M
}
// Value classes don't work with autowire
case class JobRelPath(value: String) // extends AnyVal
//sealed trait FileCommand extends Command {
// val fileContents: Map[JobRelPath, String]
//}
final case class OneShot(id: CommandId, body: String, meta: SysCmdMetaData) extends Command {
override type M = SysCmdMetaData
}
final case class Repl(id: CommandId, body: String, meta: SysCmdMetaData) extends Command{
override type M = SysCmdMetaData
}
final case class CommandInRepl(id: CommandId, body: String, meta: ReplCmdMetaData) extends Command{
override type M = ReplCmdMetaData
}
final case class ExecFile(
id: CommandId,
body: String,
meta: SysCmdMetaData,
fileContents: Map[JobRelPath, String]
) extends Command {
override type M = SysCmdMetaData
}
object Command Relevant snippets of my build.sbt: val scala211 = "2.11.8"
val scala212 = "2.12.4"
val scalaVersionSelect = scala212
val akkaHttpDep = "com.typesafe.akka" %% "akka-http" % "10.0.9"
val ammoniteDep = "com.lihaoyi" %% "ammonite-ops" % "1.0.1"
val scalatest = Def.setting(
"org.scalatest" %%% "scalatest" % "3.2.0-SNAP7" % "test")
val cats = Def.setting(
"org.typelevel" %%% "cats" % "0.9.0"
)
val autowireDeps = Def.setting(Seq(
"com.lihaoyi" %%% "autowire" % "0.2.6",
"io.suzaku" %%% "boopickle" % "1.2.6"))
val mhtmlDeps = Def.setting(Seq(
"in.nvilla" %%% "monadic-html" % "0.4.0-RC1",
"in.nvilla" %%% "monadic-rx-cats" % "0.4.0-RC1",
"org.scala-js" %%% "scalajs-dom" % "0.9.2"
))
val circeVersion = "0.10.0-M1"
val circeDeps = Def.setting(Seq(
"io.circe" %% "circe-core",
"io.circe" %% "circe-generic"
// "io.circe" %% "circe-parser" // Don't need so far
).map(_ % circeVersion))
val commonSettings = Seq(
version := "1.0-SNAPSHOT",
scalaVersion := scalaVersionSelect,
scalacOptions := Seq(
"-encoding", "UTF-8",
"-feature",
"-unchecked",
"-deprecation:false",
"-Xfatal-warnings",
//"-Xlint",
"-Xlint:-unused,_",
// "-Ywarn-unused:imports", // Disabling during normal dev - too annoying!
"-Yno-adapted-args",
"-Ywarn-numeric-widen",
"-Ywarn-value-discard",
"-Xfuture"),
resolvers ++= Seq(
Resolver.sonatypeRepo("public")
,"amateras-repo" at "http://amateras.sourceforge.jp/mvn/" // For ace editor facade
//,"sonatype-staging" at "https://oss.sonatype.org/content/repositories/staging/"
),
testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-oDF"),
// Shared /config between all projects
unmanagedClasspath in Compile <+= (baseDirectory) map { bd => Attributed.blank(bd / ".." / "config") },
unmanagedClasspath in Runtime <++= (unmanagedClasspath in Compile),
unmanagedClasspath in Test <++= (unmanagedClasspath in Compile),
libraryDependencies ++= Seq(
"org.typelevel" %%% "squants" % "1.3.0"
) ++ circeDeps.value
) // ++ warnUnusedImport // Disabling during normal dev - too annoying!
autoCompilerPlugins := true
addCompilerPlugin( // For circe generic:
"org.scalamacros" % s"paradise_$scalaVersionSelect" % "2.1.1" /*cross CrossVersion.full*/
)
val settingsJVM = commonSettings
//.... If it helps, I can try to reproduce this in a public repo. |
I don't think it is necessary with the |
question: for the sealed trait A |
…2.5.1 Update scalafmt-core to 2.5.1
Closing in cleanup run, If anyone cares about this, please comment and I'll reopen. |
As reported by @ngbinh, the following does not compile in circe 0.4.0-RC1:
The expanded version fails as well:
But can be fixed by moving the
object A
definition after the case class definitions.Similarly, it's possible to work around the issue with
@JsonCodec
by adding an object definition (potentially empty) after the case classes:This isn't that terrible, but it's an annoying thing to have to remember. I'm not sure we can fix the
JsonCodec
macro annotation so that this workaround isn't necessary, but we should at least take a look (probably after the 0.4.0 release).The text was updated successfully, but these errors were encountered: