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

