You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala3 Play框架迁移报错:找不到Conversion[models.ErrorCode, _ <: Product]实例

解决Scala3下Play Framework抽象类Json序列化编译错误

问题原因

Scala3对类型约束的检查比Scala2更严格,Play的Json.format[T]宏要求T必须是Product类型(case class默认实现该特质),但你定义的ErrorCode和AccountException是抽象类,不属于Product范畴,因此宏无法自动生成Conversion实例,触发编译报错。

解决方案

要保留抽象类的继承逻辑,需手动为抽象类实现自定义OFormat,同时为每个case class子类保留自动生成的格式,通过类型标识字段处理多态场景的序列化/反序列化。

步骤1:为ErrorCode子类生成单独格式

假设你的ErrorCode子类结构如下:

abstract class ErrorCode
case class InvalidAccountCode(message: String) extends ErrorCode
case class InsufficientBalanceCode(balance: BigDecimal) extends ErrorCode

先为每个子类自动生成OFormat:

import play.api.libs.json._

implicit val formatInvalidAccount: OFormat[InvalidAccountCode] = Json.format[InvalidAccountCode]
implicit val formatInsufficientBalance: OFormat[InsufficientBalanceCode] = Json.format[InsufficientBalanceCode]

步骤2:为抽象ErrorCode实现自定义OFormat

添加类型标识字段(比如type)区分子类:

implicit val formatErrorCode: OFormat[ErrorCode] = new OFormat[ErrorCode] {
  override def writes(errorCode: ErrorCode): JsObject = errorCode match {
    case code: InvalidAccountCode => 
      Json.toJsObject(code) + ("type" -> JsString("InvalidAccount"))
    case code: InsufficientBalanceCode => 
      Json.toJsObject(code) + ("type" -> JsString("InsufficientBalance"))
  }

  override def reads(json: JsValue): JsResult[ErrorCode] = {
    (json \ "type").validate[String].flatMap {
      case "InvalidAccount" => json.validate[InvalidAccountCode]
      case "InsufficientBalance" => json.validate[InsufficientBalanceCode]
      case unknown => JsError(s"Unknown error code type: $unknown")
    }
  }
}

步骤3:处理AccountException的序列化

假设AccountException结构如下:

abstract class AccountException(val code: ErrorCode)
case class InvalidAccountException(override val code: InvalidAccountCode) extends AccountException(code)
case class InsufficientBalanceException(override val code: InsufficientBalanceCode) extends AccountException(code)

同样先为子类生成格式,再实现抽象类的自定义格式:

// 子类格式
implicit val formatInvalidAccountEx: OFormat[InvalidAccountException] = Json.format[InvalidAccountException]
implicit val formatInsufficientBalanceEx: OFormat[InsufficientBalanceException] = Json.format[InsufficientBalanceException]

// 抽象类格式
implicit val formatAccountException: OFormat[AccountException] = new OFormat[AccountException] {
  override def writes(ex: AccountException): JsObject = ex match {
    case ex: InvalidAccountException => 
      Json.toJsObject(ex) + ("type" -> JsString("InvalidAccountException"))
    case ex: InsufficientBalanceException => 
      Json.toJsObject(ex) + ("type" -> JsString("InsufficientBalanceException"))
  }

  override def reads(json: JsValue): JsResult[AccountException] = {
    (json \ "type").validate[String].flatMap {
      case "InvalidAccountException" => json.validate[InvalidAccountException]
      case "InsufficientBalanceException" => json.validate[InsufficientBalanceException]
      case unknown => JsError(s"Unknown exception type: $unknown")
    }
  }
}

更简洁的替代方案:使用Json.formatWithDiscriminator

Play提供的formatWithDiscriminator宏可自动处理多态类型序列化,无需手动编写匹配逻辑,只需指定类型标识字段:

// 为ErrorCode及其子类配置
implicit val formatErrorCode: OFormat[ErrorCode] = Json.formatWithDiscriminator[ErrorCode]("type")
// 需确保所有子类的格式已在隐式作用域内

// 为AccountException及其子类配置
implicit val formatAccountException: OFormat[AccountException] = Json.formatWithDiscriminator[AccountException]("type")

这个宏会自动在序列化时添加类型标识字段,反序列化时根据字段匹配对应子类,默认使用子类全限定类名作为标识值,也可通过@JsonDiscriminator注解自定义。

注意事项

  • 确保所有子类的OFormat都在抽象类格式的隐式作用域内
  • 若子类包含嵌套自定义类型,需提前定义对应的隐式格式

内容的提问来源于stack exchange,提问作者DanCrts

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 14:02:39