I am learning about Scala case classes, and design patterns. To this end I have created an example below which I think is a fairly likely scenario when working with Json type data. I know libraries exist that do this stuff, but I am doing it manually to learn Scala approaches to solving problems, as using a library won't help me learn.
The main design improvement I want to do is abstracting common code.
Suppose my codebase consists of many case classes, where each case class is serializable:
trait Base {
    def serialize(): String
  }
  trait Animal extends Base
  trait Mammal extends Animal
  trait Reptile extends Animal
  case class Lizard(name: String, tail: Boolean) extends Reptile {
    def serialize(): String = s"""{name: $name, tail: $tail}"""
  }
  case class Cat(name: String, age: Int) extends Mammal {
    def serialize(): String = s"""{name: $name, age: $age}"""
  }
  case class Fish(species: String) extends Animal {
    def serialize(): String = s"""{species: $species}"""
  }
  case class Pets(group_name: String, cat: Option[Cat] = None, lizard: Option[Lizard] = None, fish: Fish) extends Base {
    def serialize(): String = {
      // cat and lizard serialize in a similar fashion
      val cat_object = cat match {
        case Some(c) => s"""cats: ${c.serialize()}"""
        case _ => ""
      }
      val lizard_object = lizard match {
        case Some(d) => s"""lizards: ${d.serialize()}"""
        case _ => ""
      }
      // fish serializes in a different way as it is not an option
      val fish_object = s"""fish: ${fish.serialize()}"""
      s"""{$lizard_object, $cat_object, $fish_object}"""
    }
  }
  val bob = Cat("Bob", 42)
  val jill = Lizard("Jill", true)
  val pets = Pets("my group", Some(bob), Some(jill), Fish("goldfish")).serialize()
  println(pets)
}
Now there is a repetitive pattern here:
- In Pets, when I am serializing, I am basically going over each (key, value) pair (except group_name) in the parameter list and doing the following: - key: value.serialize() 
Now I do not know the form of value, it can be an option as in the example. Furthermore, suppose I have many classes like Pets. In that case I would have to manually write many pattern matches on every argument where required, distinguishing between String, Int, Option[String], etc. Would there be a way to abstract out this serializable operation so that if I have many case classes like Pets, I can simply run a single function and get the correct result.
I asked a related question here about getting declared field from cases classes, but it seems that way is not type safe and may create issues later down the line if I add more custom case classes:
https://stackoverflow.com/questions/62662417/how-to-get-case-class-parameter-key-value-pairs
 
     
    