Problem Description
I have a case class Student that includes instances of the Animal trait, along with two case classes Cat and Dog that extend the trait. I'm having trouble figuring out how to properly encode and decode instances of the Student class.
Goals Understand how to correctly encode a Student instance to JSON. Learn the appropriate decoding process for a JSON representation back to a Student instance.
import io.circe._
import io.circe.generic.semiauto._
trait Animal
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal
case class Student(id: Int, name: String, animal: Animal)
object Student {
implicit val catEncoder: Encoder[Cat] = deriveEncoder
implicit val catDecoder: Decoder[Cat] = deriveDecoder
implicit val dogEncoder: Encoder[Dog] = deriveEncoder
implicit val dogDecoder: Decoder[Dog] = deriveDecoder
implicit val animalEncoder: Encoder[Animal] = deriveEncoder
implicit val animalDecoder: Decoder[Animal] = deriveDecoder
implicit val studentEncoder: Encoder[Student] = deriveEncoder
implicit val studentDecoder: Decoder[Student] = deriveDecoder
}
object Test extends App {
import io.circe.syntax._
val student = Student(1, "abc", Cat("cat"))
val jsonString = student.asJson.noSpaces
println("JSON representation: " + jsonString)
val decoded: Either[io.circe.Error, Student] = io.circe.parser.decode[Student](jsonString)
println("Decoded Student: " + decoded)
}
If you'd like to derive a type class (codec) for a parent trait of hierarchy then the trait must be sealed
sealed trait Animal
https://scastie.scala-lang.org/DmytroMitin/td1vmuCESZePhxno7VYwUw/4
//JSON representation: {"id":1,"name":"abc","animal":{"Cat":{"name":"cat"}}}
//Decoded Student: Right(Student(1,abc,Cat(cat)))
Also on contrary to Student
, for Cat
, Dog
, Animal
the instances of type classes Encoder
/Decoder
are placed now out of implicit scope.
Where does Scala look for implicits?
Implicits of types SomeTypeclass[SomeDatatype]
should be placed to companion objects of SomeTypeclass
or SomeDatatype
. Then the implicits will be available without import. You can check that although implicitly[Decoder[Student]]
compiles but for example implicitly[Decoder[Cat]]
doesn't. So you should better write
sealed trait Animal
object Animal {
implicit val animalEncoder: Encoder[Animal] = deriveEncoder
implicit val animalDecoder: Decoder[Animal] = deriveDecoder
}
case class Cat(name: String) extends Animal
object Cat {
implicit val catEncoder: Encoder[Cat] = deriveEncoder
implicit val catDecoder: Decoder[Cat] = deriveDecoder
}
case class Dog(name: String) extends Animal
object Dog {
implicit val dogEncoder: Encoder[Dog] = deriveEncoder
implicit val dogDecoder: Decoder[Dog] = deriveDecoder
}
case class Student(id: Int, name: String, animal: Animal)
object Student {
implicit val studentEncoder: Encoder[Student] = deriveEncoder
implicit val studentDecoder: Decoder[Student] = deriveDecoder
}
https://scastie.scala-lang.org/DmytroMitin/td1vmuCESZePhxno7VYwUw/2
Also Encoder
+ Decoder
is just Codec
sealed trait Animal
object Animal {
implicit val animalCodec: Codec[Animal] = deriveCodec
}
case class Cat(name: String) extends Animal
object Cat {
implicit val catCodec: Codec[Cat] = deriveCodec
}
case class Dog(name: String) extends Animal
object Dog {
implicit val dogCodec: Codec[Dog] = deriveCodec
}
case class Student(id: Int, name: String, animal: Animal)
object Student {
implicit val studentCodec: Codec[Student] = deriveCodec
}
https://scastie.scala-lang.org/DmytroMitin/td1vmuCESZePhxno7VYwUw/5