使用Scala Play解析JSON时如何对未知额外字段抛出错误
解决方案
Play JSON 提供的宏生成的 Reads 默认会忽略 JSON 中 case class 未定义的额外字段,不会抛出错误。要实现类似JSON Schema中additionalProperties: false的校验效果,可以通过自定义 Reads 增加字段校验逻辑实现,具体方案如下:
方案1:手动为指定case class编写带额外字段校验的Reads
核心思路是先校验JSON对象的所有键是否都在允许的字段列表中,校验通过后再执行常规的字段解析,示例代码如下:
import play.api.libs.json._ case class MyThing(fieldOne: Option[String]) object MyThing { // 定义允许的字段列表 private val allowedFields = Set("fieldOne") implicit val reads: Reads[MyThing] = new Reads[MyThing] { override def reads(json: JsValue): JsResult[MyThing] = json match { case JsObject(fields) => // 提取所有传入的字段名 val inputFields = fields.keySet // 找出不在允许列表中的额外字段 val extraFields = inputFields -- allowedFields if (extraFields.nonEmpty) { JsError(s"检测到未知字段:${extraFields.mkString(", ")}") } else { // 校验通过后用宏生成的Reads解析 Json.reads[MyThing].reads(json) } case _ => JsError("期望输入为JSON对象") } } }
该实现下,当传入包含fieldOne之外字段的JSON时,会直接返回JsError,符合校验需求。
方案2:封装通用校验逻辑(适用于多case class场景)
如果有大量case class需要做相同的额外字段校验,可以封装通用的校验方法,避免重复代码:
import play.api.libs.json._ object JsonValidationUtils { def withExtraFieldCheck[T](allowedFields: Set[String], underlying: Reads[T]): Reads[T] = new Reads[T] { override def reads(json: JsValue): JsResult[T] = json match { case JsObject(fields) => val extraFields = fields.keySet -- allowedFields if (extraFields.nonEmpty) { JsError(s"未知字段:${extraFields.mkString(", ")}") } else { underlying.reads(json) } case _ => JsError("输入必须为JSON对象") } } } // 使用示例 case class MyThing(fieldOne: Option[String]) object MyThing { import JsonValidationUtils._ implicit val reads: Reads[MyThing] = withExtraFieldCheck( allowedFields = Set("fieldOne"), underlying = Json.reads[MyThing] ) }
注意事项
- 如果是嵌套的case class结构,需要为每一层的case class的
Reads都加上额外字段校验,否则嵌套对象内的未知字段无法被检测到 - 若需要兼容Play自带的错误格式规范,可以调整
JsError的返回结构,和Play默认的字段错误格式对齐
内容的提问来源于stack exchange,提问作者James
相关产品推荐
相关产品推荐

