6

Hello I'm having trouble with a JSON body parser. My problem is the following I have a case class with some optional parameters (Option[T]) and a parameter with a default value which I do not want to be typed as Option[T].

However when parsing a JSON body having the field with the default value omitted I get an error

play.api.libs.JsError
/count error path missing

Here is my controller code :

object MyController extends Controller{


  implicit val itemWrites = Json.writes[Item]
  implicit val itemReads = Json.reads[Item]
  implicit val itemFormats = Json.format[Item]

  def add = DBAction(parse.json){ implicit rs =>

    val item =  rs.request.body.validate[Item]
}

Here is my case class :

case class Item( id:Option[Int], name:String, description:Option[String], count:Int=0)

Any chance I can achieve the same behavior as Option[T] with the default value field?

Thanks

I'm using :

  • Scala 2.10
  • Play Framework 2.2.1
  • Play-Slick plugin 0.5.0.8
ufasoli
  • 1,038
  • 2
  • 19
  • 41

2 Answers2

4

Almost. You can define a default value with an Option like this:

case class Item( description:Option[String] = Some("String"))

If you definitely do not want an option, you can have a look here:

Defaults for missing properties in play 2 JSON formats

Community
  • 1
  • 1
Maxime Dantec
  • 562
  • 2
  • 11
  • Hi, thanks I would rather not have an option. As for the link you provided. The user states his current solution has some drawbacks and I must say I agree. However if no better solution is suggested I will end-up using his approach as I do not see a way around – ufasoli Dec 17 '13 at 12:25
  • The best solution would involve writing a macro to automatically create the withDefault, this is quite the undertaking though :( – Jean Dec 17 '13 at 14:09
1

One workaround would be to write an apply method that takes in the default value of count as an option and handles the construction (can't name it apply as we need an unambiguous name when building our Reads):

object Item{
  def applyOpt(id:Option[Int], name:String, description:Option[String], count:Option[Int]): Item = count.map{c =>
    Item(id, name, description, c)
  }.getOrElse{
    Item(id, name, description)
  }
}

Then you could use readNullable for the default value, which will pass an Option[Int] to applyOpt:

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


implicit val itemReads: Reads[Item] = (
  (__ \ "id").readNullable[Int] and
  (__ \ "name").read[String] and
  (__ \ "description").readNullable[String] and
  (__ \ "count").readNullable[Int]
)(Item.applyOpt _)

Certainly not ideal, especially if you have several default fields, but a quick workaround that avoids having to deal with macros or reflection.

josephpconley
  • 1,703
  • 12
  • 12
  • yes but I was hoping to avoid writing the Reader with all the fields as a have a a lot case classes with the same scenario but more fields which is tedious to write and error prone when model is not yet 100% stable and you need to update it, I was hoping it would just work out of the box as Jackson does in Java – ufasoli Dec 18 '13 at 05:57