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

如何将JSON映射为Case Class并限制result字段枚举值

我懂你要的效果——把指定结构的JSON映射成Scala Case Class,而且result字段必须严格只能是"success"或"error",非法值直接触发解析失败对吧?你用密封特质的方向是对的,只是还需要补全类型约束和解析层的校验逻辑,我给你一步步讲清楚:

1. 先从类型层面锁死取值范围

不要让子类的result字段允许动态传值,而是把result作为密封特质的抽象成员,再用单例/固定值的子类来限定合法选项,这样编译期就能杜绝非法值:

// 基础密封特质,定义必须有的result字段
sealed trait JSONResult {
  val result: String
}

// 用单例实现固定值的成功结果
case object JSONResultSuccess extends JSONResult {
  override val result: String = "success"
}

// 用单例实现固定值的错误结果
case object JSONResultError extends JSONResult {
  override val result: String = "error"
}

如果你的JSON还包含message字段,那可以把结构调整为带message的密封特质:

sealed trait APIResponse {
  val result: String
  val message: String
}

case class SuccessResponse(message: String) extends APIResponse {
  override val result: String = "success" // 固定死result值
}

case class ErrorResponse(message: String) extends APIResponse {
  override val result: String = "error" // 固定死result值
}

这种写法下,编译期就不可能创建出result为其他值的实例,从根源上避免了非法值。

2. 结合JSON解析库实现解析校验

光有类型定义还不够,还要让JSON解析时,遇到非"success"/"error"的result直接返回失败。这里以Scala常用的Circe库为例:

首先添加Circe依赖(sbt):

libraryDependencies ++= Seq(
  "io.circe" %% "circe-core" % "0.14.6",
  "io.circe" %% "circe-generic" % "0.14.6",
  "io.circe" %% "circe-parser" % "0.14.6"
)

然后自定义解码器,实现result字段的校验逻辑:

import io.circe._
import io.circe.generic.semiauto._

object APIResponse {
  // 自动生成子类的解码器
  private val successDecoder: Decoder[SuccessResponse] = deriveDecoder[SuccessResponse]
  private val errorDecoder: Decoder[ErrorResponse] = deriveDecoder[ErrorResponse]

  // 自定义顶层解码器,校验result的值
  implicit val decoder: Decoder[APIResponse] = Decoder.instance { cursor =>
    cursor.get[String]("result").flatMap {
      case "success" => cursor.as[SuccessResponse](successDecoder)
      case "error" => cursor.as[ErrorResponse](errorDecoder)
      case invalidValue => 
        Left(DecodingFailure(s"Invalid result value: '$invalidValue', must be 'success' or 'error'", cursor.history))
    }
  }

  // 自动生成编码器
  implicit val encoder: Encoder[APIResponse] = Encoder.instance {
    case s: SuccessResponse => deriveEncoder[SuccessResponse].apply(s)
    case e: ErrorResponse => deriveEncoder[ErrorResponse].apply(e)
  }
}

测试一下解析效果:

import io.circe.parser._

// 合法的success JSON
val validSuccessJson = """{"result":"success","message":"操作完成"}"""
decode[APIResponse](validSuccessJson) // 返回 Right(SuccessResponse("操作完成"))

// 合法的error JSON
val validErrorJson = """{"result":"error","message":"权限不足"}"""
decode[APIResponse](validErrorJson) // 返回 Right(ErrorResponse("权限不足"))

// 非法的result值
val invalidJson = """{"result":"unknown","message":"无效请求"}"""
decode[APIResponse](invalidJson) // 返回 Left(DecodingFailure(...)) 解析失败
3. 为什么不推荐你之前的写法?

你尝试的case class JSONResultError(final val result:String="error")这种方式,其实还是允许传入其他值(比如JSONResultError("invalid")),完全没起到限制取值范围的作用。而用固定result值的子类/单例,才能从编译期就保证合法性。

4. 其他JSON库的实现思路(以Play JSON为例)

如果用Play JSON,核心逻辑和Circe一致:自定义Reads来校验result字段:

import play.api.libs.json._

object APIResponse {
  implicit val successReads: Reads[SuccessResponse] = Json.reads[SuccessResponse]
  implicit val errorReads: Reads[ErrorResponse] = Json.reads[ErrorResponse]

  implicit val apiResponseReads: Reads[APIResponse] = Reads { json =>
    (json \ "result").validate[String].flatMap {
      case "success" => json.validate[SuccessResponse]
      case "error" => json.validate[ErrorResponse]
      case invalid => JsError(s"Invalid result: '$invalid', must be 'success' or 'error'")
    }
  }

  implicit val apiResponseWrites: Writes[APIResponse] = Writes {
    case s: SuccessResponse => Json.writes[SuccessResponse].writes(s)
    case e: ErrorResponse => Json.writes[ErrorResponse].writes(e)
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:11:23