Joe
Joe

Reputation: 1723

Playframework: How do I bind JSON to case class that contains a map?

I'm using Playframework 2.2.1 with Scala, and I'm writing a REST API where users can set partially unstructured data. Basically, users will POST JSON that looks something like this:

{
  "id": 1,
  "name": "MyObject",
  "properties": [
    "myFirstProperty": "Value 1",
    "mySecondProperty": "Value 2"
  ]
}

and I want it to bind to a case class that looks like:

case class Preference(id: Long, name: String, properties: Map[String, String])

I am hoping to take advantage of Play's forms API for that so I can have a bunch of nice validation options for free, but I haven't been able to figure out a straightforward way to do that. Ideally (at least in my mind), I would be able to define a Form object along the lines of:

Form(
  mapping(
    "id" -> longNumber,
    "name" -> nonEmptyText(min = 5),
    "properties" -> map(nonEmptyText, nonEmptyText)
  )(Preference.apply)(Preference.unapply)
)

Unfortunately, the "map(text, text)" is fictional. Has anyone done a similar binding to this using the forms API? If so, how? If not, I'm curious what you used instead for validation. Was it all done by hand?

Thanks in advance for the help!

Upvotes: 4

Views: 1306

Answers (2)

Aaron Novstrup
Aaron Novstrup

Reputation: 21017

Maybe I'm misunderstanding your question, but doesn't the reads macro do what you want?

import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val preferenceReads = Json.reads[Preference]

With that implicit in scope, you can use as, asOpt, or validate to convert your JSON value to a Preference (see the Play ScalaJson docs).

Upvotes: 2

tmbo
tmbo

Reputation: 1317

As far as I know the forms API and the json support are not meant to be mixed together. But that shouldn't be a problem since you can accomplish pretty much every validation with either forms or json reads.

import play.api.libs.json._
import play.api.libs.functional.syntax._
import play.api.libs.json.Reads._
import play.api.data.validation.ValidationError

val nonEmptyKeyValueMapReads = 
  filter[Map[String, String]](ValidationError("error.invalid"))(properties =>
    properties.keys.find(_.size == 0).isEmpty && properties.values.find(_.size == 0).isEmpty)

val preferencesReads =
  ((__ \ 'id).read[Long] and
    (__ \ 'name).read[String](minLength[String](5)) and
    (__ \ 'properties).read[Map[String, String]](nonEmptyKeyValueMapReads))(Preferences)

There are several ways to achieve your properties, this is one of them. After defining the reads you can use it to validate your data, e.g. myJson.validate(preferencesReads)

Upvotes: 1

Related Questions