playframework/play-json

automatic sealed trait discriminator takes precedence over apply/unapply methods

tnielens opened this issue · 0 comments

There seems to be incompatible change between versions 2.4.x and 2.6.14 regarding the sealed types Format instance macro.

Play JSON Version

2.4.x -> 2.6.14

API (Scala / Java / Neither / Both)

Scala

Expected Behavior

Using the older version of play-json, I have defined a Format instance of my sealed trait TableColumn type. See snippet bellow. I use the macro which requires apply and unapply methods to be defined on the trait. The macros uses these methods for the serialization and the discriminator field name. The discriminator logic is implemented manually.

As I upgrade to later versions of play-json, I expect the same behavior.

Actual Behavior

The new sealed trait discriminator feature takes precedence over my manual implementation.

Reproducible Test Case

sealed trait TableColumn[T] {
  val name: String
  val values: List[T]
}

case class DoubleTableColumn(name: String, values: List[Double]) extends TableColumn[Double]
case class StringTableColumn(name: String, values: List[String]) extends TableColumn[String]

// companion object of trait is needed, since apply() and unapply() functions are required on TableColumn
object TableColumn {

  def unapply(tableColumn: TableColumn[_]): Option[(String, JsValue)] = {
    val (prod: Product, jsValue) = tableColumn match {
      case column: DoubleTableColumn => (column, Json.toJson(column)(doubleTableColumnFormat))
      case column: StringTableColumn => (column, Json.toJson(column)(stringTableColumnFormat))
    }
    Some(prod.productPrefix -> jsValue)
  }

  def apply(className: String, data: JsValue): TableColumn[_] = {
    (className match {
      case "DoubleTableColumn" => Json.fromJson[DoubleTableColumn](data)(doubleTableColumnFormat)
      case "StringTableColumn" => Json.fromJson[StringTableColumn](data)(stringTableColumnFormat)
      case _ => throw new IllegalArgumentException(s"className '$className' not recognized.")
    }).get
  }
    
  implicit lazy val doubleTableColumnFormat: Format[DoubleTableColumn] = Json.format[DoubleTableColumn]
  implicit lazy val stringTableColumnFormat: Format[StringTableColumn] = Json.format[StringTableColumn]

  // this instance uses the default discriminator field "_type" instead of the apply/unapply methods logic
  implicit lazy val tableColumnFormat: Format[TableColumn[_]] = Json.format[TableColumn[_]]
}