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
相关产品推荐
相关产品推荐

