With JSON schemas, if you want the schema to fail validation if have any additional fields you can just throw an "additionalProperties": false
on the schema and call it a day a bit like this:
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"title": "",
"description": "",
"properties": {
"fieldOne": {
"type": "string",
"description": "Example String"
}
},
"additionalProperties": false
}
However, if I have the following case class/object:
case class MyThing(fieldOne: Option[String])
object MyThing {
implicit val reads: Reads[MyThing] = Json.reads[MyThing]
}
and provide it stuff other than fieldOne
, it'll still read the JSON in as a case class correctly but the case class would be empty.
Is there a way to error when additional fields are provided in JSON when reading from JSON to a case class?
CodePudding user response:
Play JSON doesn't natively have this but in a custom Reads
you have access to the JsValue
/JsObject
from the simple parse. So for something simple, you could do something like:
object MyThing {
// Single-abstract method should work, if not more explicitly extend Reads
implicit val reads: Reads[MyThing] = { json: JsValue =>
json match {
case JsObject(kv) =>
val keys = kv.keySet
if (keys != expectedFields) {
(keys -- expected).headOption.map { unexpected =>
JsError(s"Encountered unexpected field $unexpected")
}.getOrElse(JsError("Must be a non-empty object"))
} else derivedReads.reads(json)
case _ => JsError(JsonValidationError("must be an object"))
}
}
private val expectedFields = Set("fieldOne")
private val derivedReads = Json.reads[MyThing]
}
In general, assuming you have a corresponding Writes
which obeys a round-trip property you can do something like:
def strictify[T](reads: Reads[T], writes: Writes[T]): Reads[T] = new Reads[T] {
def reads(json: JsValue): JsResult =
reads.reads(json).filter { t =>
val writeback = writes.writes(t)
writeback == json
}
}
This is strictly less-efficient than checking in a custom Reads
, but it does enable
object MyThing {
implicit val writes: Writes[MyThing] = Json.writes[MyThing]
implicit val reads: Reads[MyThing] = strictify(Json.reads[MyThing], writes)
}
which would probably win if clarity is more important than performance.