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

Play Framework中Scala 3枚举的JSON与表单绑定实现问题

解决Scala 3枚举在Play Framework表单与JSON中的适配问题

核心问题拆解

你遇到的报错本质是Play表单系统找不到Frequency枚举对应的Formatter类型类——这和JSON序列化是两套独立机制,你之前尝试的EnumFormat、Json.formatEnum只解决JSON读写,表单绑定需要单独实现Formatter[Frequency]。另外代码里还有两处明显问题:

  • case class A手动定义的unapply完全错误,case class会自动生成正确的unapply方法,无需手写
  • 枚举内的implicit定义位置不合理,应该放在枚举的伴生对象中,确保作用域可访问

分步解决方案

1. 修正case class A的表单定义

移除错误的手动unapply,将表单定义放在伴生对象中更规范:

case class A(freq: Frequency)

object A {
  val form: Form[A] = Form(
    mapping(
      "freq" -> of[Frequency]
    )(A.apply)(A.unapply)
  )
}

2. 为Scala 3枚举实现表单Formatter

Play表单依赖Formatter[T]完成字符串与枚举值的双向转换,为Frequency添加implicit实现:

import play.api.data.FormError
import play.api.data.format.Formatter

enum Frequency {
  case None, Daily, Weekly
}

object Frequency {
  // 表单绑定用Formatter:处理字符串转枚举、枚举转字符串
  implicit val frequencyFormatter: Formatter[Frequency] = new Formatter[Frequency] {
    override def bind(key: String, data: Map[String, String]): Either[Seq[FormError], Frequency] = {
      data.get(key) match {
        // 用枚举的values方法匹配,避免硬编码每个case
        case Some(name) => 
          Frequency.values.find(_.toString == name)
            .toRight(Seq(FormError(key, s"无效的频率值: $name", Nil)))
        case None => Left(Seq(FormError(key, "频率为必填项", Nil)))
      }
    }

    override def unbind(key: String, value: Frequency): Map[String, String] = {
      Map(key -> value.toString)
    }
  }
}

3. 简化JSON序列化(可选)

如果使用Play JSON 2.10+版本,Scala 3枚举无需额外依赖第三方工具,直接用官方API即可:

import play.api.libs.json._

object Frequency {
  // 接上面的Formatter代码,添加JSON Format
  implicit val frequencyFormat: OFormat[Frequency] = Json.formatEnum(Frequency)
  
  // 也可以用派生语法更简洁(需要导入AutoFormat)
  // implicit val frequencyFormat: OFormat[Frequency] = DeriveOFormat.gen[Frequency]
}

完整可运行代码示例

import play.api.data.Form
import play.api.data.FormError
import play.api.data.format.Formatter
import play.api.data.mapping
import play.api.libs.json._

enum Frequency {
  case None, Daily, Weekly
}

object Frequency {
  // 表单绑定Formatter
  implicit val frequencyFormatter: Formatter[Frequency] = new Formatter[Frequency] {
    override def bind(key: String, data: Map[String, String]): Either[Seq[FormError], Frequency] = {
      data.get(key) match {
        case Some(name) => 
          Frequency.values.find(_.toString == name)
            .toRight(Seq(FormError(key, s"无效的频率值: $name", Nil)))
        case None => Left(Seq(FormError(key, "频率为必填项", Nil)))
      }
    }

    override def unbind(key: String, value: Frequency): Map[String, String] = {
      Map(key -> value.toString)
    }
  }

  // JSON序列化Format
  implicit val frequencyFormat: OFormat[Frequency] = Json.formatEnum(Frequency)
}

case class A(freq: Frequency)

object A {
  val form: Form[A] = Form(
    mapping(
      "freq" -> of[Frequency]
    )(A.apply)(A.unapply)
  )
}

关键注意事项

  • 表单绑定用Formatter[T],JSON序列化用Format[T],二者是完全独立的类型类,不要混淆
  • Scala 3枚举自带values方法,可以获取所有枚举实例,用来简化字符串匹配逻辑
  • 所有implicit定义放在枚举的伴生对象中,确保在表单、JSON序列化的作用域内能够被自动找到

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 16:05:55