Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 815
dccollie Avatar asked Apr 25 '14 16:04

dccollie


2 Answers

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.

like image 186
Travis Brown Avatar answered Oct 20 '22 00:10

Travis Brown


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)

like image 33
Vikas Pandya Avatar answered Oct 20 '22 01:10

Vikas Pandya