0
votes

I have a class Person and class Employee. Class Employee extends class Person.

My current implicit writes to convert classes to JSON looks like this:

implicit val implicitPersonWrites = new Writes[Person] {
  def writes(v: Person): JsValue = {
    Json.obj("label" -> v.label, "age" -> v.age)
  }
}

implicit val implicitEmployeeWrites = new Writes[Employee] {
  def writes(v: Employee): JsValue = {
    Json.obj("label" -> v.label, "age" -> v.age, "company" -> v.company)
  }
}

The problem is that even my object is of type Employee, implicit write for Person superclass is always used. So at the end the fields specific to Employee class are not present in the output. How to make implicit writes properly in case of inheritance?

1
Implicits are resolved statically, so if you've got something of type Person the Person instance will be used, even if it's "really" an Employee. - Travis Brown
I understand, but how to make Employee to be converted as Employee, and not Person? I found solution (probably ugly) when I write implicit writer for Person and I distinguish between Person and Employee in its body with match/case (case v:Employee => Json.obj ...) - user3024710

1 Answers

1
votes

Three options:

  1. (Best in most circumstances) Use composition instead of inheritance. Instead of class Employee(...) extends Person(...)) have class Employee(person: Person, company: String).

  2. If you have a fixed hierarchy of subclasses, then, as you mention in the comment, dispatch on the type in implicitPersonWrites. This isn't particularly ugly; this happens for Option, List, etc. all the time. Ideally you can turn Person into an algebraic data type.

  3. Use the inheritance you have:

    implicit val implicitPersonWrites = new Writes[Person] {
      def writes(v: Person): JsValue = v.toJsValue
    }
    
    // no implicitEmployeeWrites 
    
    class Person(...) {
      def toJsValue = Json.obj("label" -> label, "age" -> age)
    }
    
    class Employee(...) extends Person(...) {
      override def toJsValue = Json.obj("label" -> label, "age" -> age, "company" -> company)
    }
    

    This has the obvious drawbacks:

    1. Person and subclasses now have to know about JsValues;

    2. This can only be done for cases like Writes where the type argument is used as an argument.