简体   繁体   中英

How to enforce strict serialization of JSON in Play 2.x

Play's JSON serialization is by default permissive when serializing from JSON into a case class. For example.

case class Stuff(name: String, value: Option[Boolean])

implicit val stuffReads: Reads[Stuff] = (
  ( __ \ 'name).read[String] and
  ( __ \ 'value).readNullable[Boolean]
)(Stuff.apply _)

If the following JSON was received:

{name: "My Stuff", value: true, extraField: "this shouldn't be here"}

It will succeed with a 'JsSuccess' and discard the 'extraField'.

Is there a way to construct the Json Reads function to have it return a JsError if there are 'unhandled' fields?

You can verify that the object doesn't contain extra keys before performing your own decoding:

import play.api.data.validation.ValidationError

def onlyFields(allowed: String*): Reads[JsObject] = Reads.filter(
  ValidationError("One or more extra fields!")
)(_.keys.forall(allowed.contains))

Or if you don't care about error messages (and that one's not very helpful, anyway):

def onlyFields(allowed: String*): Reads[JsObject] =
  Reads.verifying(_.keys.forall(allowed.contains))

And then:

implicit val stuffReads: Reads[Stuff] = onlyFields("name", "value") andThen (
  (__ \ 'name).read[String] and
  (__ \ 'value).readNullable[Boolean]
)(Stuff)

The repetition isn't very nice, but it works.

Inspired from Travis ' comment to use LabelledGeneric I was able achieve compile time safe solution.

object toStringName extends Poly1 {
    implicit def keyToStrName[A] = at[Symbol with A](_.name)
}
case class Foo(bar: String, boo: Boolean)

val labl = LabelledGeneric[Foo]
val keys = Keys[labl.Repr].apply

now keys.map (toStringName).toList will give you

res0: List[String] = List(bar, boo)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM