Encoding and Decoding for Student Case Class with Animal Trait

75 Views Asked by At

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)
}

1

There are 1 best solutions below

0
On BEST ANSWER

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