1

I am writing to write encoder/decoder for Any. So suppose my class is

case class Demo(
field1: Any
)

and then I try to encoder it via

val myDemo=Demo(field1=None)
print(myDemo.asJson+"\n")

I have definded encoder decoder as

  implicit val valueEncoderValue: Encoder[Any] = Encoder.encodeString.contramap[Any](x=>{
    x.toString})

  implicit val valueDecoderValue: Decoder[Any] = Decoder.decodeString.map[Any](x => {
    if (x == "Any")
      x.asInstanceOf[Any]
    else
      x.toString
  })

  implicit lazy val DemoCodec: Codec[Demo] =
    deriveCodec[Demo]

I am getting the error

Exception in thread "main" java.lang.ClassCastException: class scala.None$ cannot be cast to class shapeless.labelled$KeyTag (scala.None$ and shapeless.labelled$KeyTag are in unnamed module of loader 'app')
    at TestDataCodecs$anon$lazy$macro$19$2$$anon$10.encodeObject(TestDataCodecs.scala:62)
    at TestDataCodecs$anon$lazy$macro$19$2$$anon$10.encodeObject(TestDataCodecs.scala:62)
    at io.circe.generic.codec.DerivedAsObjectCodec$$anon$1.encodeObject(DerivedAsObjectCodec.scala:21)
    at io.circe.Encoder$AsObject.apply(Encoder.scala:826)
    at io.circe.Encoder$AsObject.apply$(Encoder.scala:826)
    at io.circe.generic.codec.DerivedAsObjectCodec.apply(DerivedAsObjectCodec.scala:6)
    at io.circe.syntax.package$EncoderOps$.asJson$extension(package.scala:10)
    at MyClass$.main(test.scala:171)
    at MyClass.main(test.scala)

Seems pretty simple encoder decoder I dont understamd the cause of this error. Can anyone help define encoder decoder for this in scala

My depedencies file:

"io.circe" %% "circe-core" % "0.13.0",
"io.circe" %% "circe-parser" % "0.13.0",
"io.circe" %% "circe-generic" % "0.13.0",
"io.circe" %% "circe-generic-extras" % "0.13.0",
"com.typesafe.akka"     %% "akka-http"         %  "10.2.4"
Freez
  • 53
  • 9

1 Answers1

1

You can investigate how implicits are resolved now with reify {...} or -Xprint:typer. Then it will be seen what causes ClassCastException.

In scala 2 or 3, is it possible to debug implicit resolution process in runtime?

I haven't investigated yet how implicits are resolved now. Below is just what I think so far.

Firstly, when you define Encoder[Any] you define an encoder literally for everything. And you define it locally, so this is a high-priority implicit, which fits for everything. Apparently, this breaks something in Circe machinery. shapeless.labelled.KeyTag starts to be encoded not as expected etc.

Such implicits should be of low priority. Normally, implicits of low priority are defined with LowPriority pattern

trait LowPriority1 {
  // implicits
}
trait LowPriority extends LowPriority1 {
  // implicits
}
object CompanionObject extents LowPriority {
  // implicits
}

But you define it locally. It's tricky to define low-priority local implicits. For example you can use shapeless.LowPriority

implicit def valueEncoderValue(implicit lp: LowPriority): Encoder[Any] ...

Secondly, types Nothing and Any play special role in implicit resolution. Compiler tries to avoid to infer them during implicit resolution. You can check that even AnyRef can improve the situation. Workarounds are to use type T <: Nothing and similarly we can try type T >: Any.

Failed implicit resolution for Nothing with <:<

Thirdly, when you define a type A, normally the best place for implicits Encoder[A], Decoder[A], Codec[A] are in the companion object of A. This can improve automatic/semi-automatic derivation.

So as a workaround try

import io.circe.generic.semiauto.deriveEncoder
import io.circe.Encoder
import shapeless.LowPriority
import io.circe.syntax._

type T >: Any
case class Demo(
                 field1: T
               )

val myDemo = Demo(field1 = None)
print(myDemo.asJson+"\n")

object Demo {
  implicit val DemoCodec: Encoder[Demo] = deriveEncoder[Demo]
}

implicit def valueEncoderValue(implicit lp: LowPriority): Encoder[T] = Encoder.encodeString.contramap[T](x=>{
  x.toString})

https://scastie.scala-lang.org/DmytroMitin/gaZw3M0zQ6qwNwhqD1nq5g/1

Generally, defining Encoder[Any] looks weird. This looks like kind of abusing type-based codec derivation.


-Xprint:typer shows that deriveCodec[Demo] and myDemo.asJson are resolved in the following way

https://scastie.scala-lang.org/DmytroMitin/gaZw3M0zQ6qwNwhqD1nq5g/3


I was debugging and found the reasons for runtime exceptions. ClassCastException is because shapeless.labelled.KeyTag is a trait rather than an abstract type. Normally this works but not for Any. Not sure whether this is a bug. https://github.com/milessabin/shapeless/issues/1285

type FieldType[K, +V] = V with KeyTag[K, V]
trait KeyTag[K, +V] 

trait Tagged[U]
type @@[+T, U] = T with Tagged[U]

None.asInstanceOf[FieldType[Symbol @@ "field1", Any]]
// scala.None$ cannot be cast to KeyTag
type FieldType[K, +V] = V with KeyTag[K, V]
type KeyTag[K, +V] 

trait Tagged[U]
type @@[+T, U] = T with Tagged[U]

None.asInstanceOf[FieldType[Symbol @@ "field1", Any]] // no exception

You can try patched versions of Shapeless and Circe-generic with KeyTag being an abstract type rather than trait

scalaVersion := "2.13.10"
resolvers ++= Resolver.sonatypeOssRepos("releases")
libraryDependencies ++= Seq(
  "io.circe" %% "circe-core"   % "0.14.3",
  "io.circe" %% "circe-parser" % "0.14.3",
  "com.github.dmytromitin" %% "circe-generic-patched-type-keytag" % "0.14.3",
  // "io.circe" %% "circe-generic" % "0.14.3" exclude("com.chuusai", "shapeless_2.13"),
  "com.github.dmytromitin" %% "shapeless-patched-type-keytag" % "2.3.10",
  // "com.chuusai" %% "shapeless" % "2.3.10",
)

https://github.com/DmytroMitin/shapeless-circe-patched-type-keytag

Then your code throws NullPointerException (ClassCastException is already fixed)

object Main extends App {
  case class Demo(
                   field1: Any
                 )

  val myDemo = Demo(field1 = None)
  print(myDemo.asJson + "\n")

  implicit val valueEncoderValue: Encoder[Any] = Encoder.encodeString.contramap[Any](x => {
    x.toString
  })

  implicit val valueDecoderValue: Decoder[Any] = Decoder.decodeString.map[Any](x => {
    if (x == "Any")
      x.asInstanceOf[Any]
    else
      x.toString
  })

  implicit lazy val DemoCodec: Codec[Demo] =
    deriveCodec[Demo]
}

NullPointerException is because of the initialization order in the object. You can check that

val myDemo = Demo(field1 = None)
implicit val valueEncoderValue: Encoder[Any] = Encoder.encodeString.contramap[Any](_.toString)
implicit val DemoCodec: Codec[Demo] = deriveCodec[Demo]
print(myDemo.asJson + "\n")
//{
//  "field1" : "None"
//}

works properly but

val myDemo = Demo(field1 = None)
implicit val valueEncoderValue: Encoder[Any] = Encoder.encodeString.contramap[Any](_.toString)
print(myDemo.asJson + "\n")
implicit val DemoCodec: Codec[Demo] = deriveCodec[Demo]

or

val myDemo = Demo(field1 = None)
implicit val DemoCodec: Codec[Demo] = deriveCodec[Demo]
print(myDemo.asJson + "\n")
implicit val valueEncoderValue: Encoder[Any] = Encoder.encodeString.contramap[Any](_.toString)

throw NullPointerException. The thing is that an implicit (a val) is used when in the object it's not initialized yet. A solution is to use lazy val

print(myDemo.asJson + "\n")
lazy val myDemo = Demo(field1 = None)
implicit lazy val DemoCodec: Codec[Demo] = deriveCodec[Demo]
implicit lazy val valueEncoderValue: Encoder[Any] = Encoder.encodeString.contramap[Any](_.toString)
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Hi I am not able to define type T >: Any when the package name is given. I am getting error "Wrong top statement declaration" – Freez Nov 10 '22 at 10:02
  • [T >: Any] using this fixes the issues case class Demo[T >: Any] – Freez Nov 10 '22 at 10:04
  • @Dymtro Can you help me with this: I am not able to define type T >: Any when the package name is given for the class. I am getting error "Wrong top statement declaration" – Freez Nov 10 '22 at 10:27
  • @Freez Abstract-type declarations can't be top-level. Scastie wraps an input into some object. So you can consider my code as if everything is inside some object. If you need to have an abstract type on top level I guess you can emulate this putting it into a package object. – Dmytro Mitin Nov 10 '22 at 10:31
  • https://scastie.scala-lang.org/DmytroMitin/C0FHVzqOQOu1Rw6bcRiFbg https://github.com/DmytroMitin/shapeless-circe-patched-type-keytag – Dmytro Mitin Nov 11 '22 at 12:55
  • @Freez I was debugging and found the reasons for runtime exceptions. See the update. – Dmytro Mitin Nov 12 '22 at 18:26