Search code examples
jsonscalaplayframework-2.0playframework-json

Non-implicit Writes can't deal with Option (Scala, JSON, Play 2.3.6)


I have a case class like this:

case class Product(ean: Long, name: String, description: String, purchasePrice: Option[BigDecimal] = None, sellingPrice: Option[BigDecimal] = None)

And I have a non-implicit Writes like this:

val adminProductWrites = (
  (JsPath \ "ean").write[Long] and
  (JsPath \ "name").write[String] and
  (JsPath \ "description").write[String] and
  (JsPath \ "purchase_price").writeNullable[BigDecimal] and
  (JsPath \ "selling_price").writeNullable[BigDecimal]
)(unlift(Product.unapply))

And then I have an instance of Option[Product]:

val prod = Some(Product(5018206244611L, "Zebra Paperclips", "Zebra Length 28mm Assorted 150 Pack"))

When I tried to serialise it...:

val jsonStr = Json.toJson(prod) (adminProductWrites)

I got an error like this:

<console>:21: error: type mismatch;
 found   : play.api.libs.json.OWrites[Product]
 required: play.api.libs.json.Writes[Some[Product]]
       val jsonStr = Json.toJson(prod) (adminProductWrites)

So, first (just for comparison) I tried this:

 val jsonStr = Json.toJson(prod.get) (adminProductWrites)

It works:

jsonStr: play.api.libs.json.JsValue = {"ean":5018206244611,"name":"Zebra Paperclips","description":"Zebra Length 28mm Assorted 150 Pack"}

But I don't want to do that (calling .get). I need to work the way it does when the Writes is declared as implicit:

implicit object ProductWrites extends Writes[Product] {
  def writes(p: Product) = Json.obj(
    "ean" -> Json.toJson(p.ean),
    "name" -> Json.toJson(p.name),
    "description" -> Json.toJson(p.description)
  )
}

(With that implicit Writes, this line works):

scala> val jsonStr = Json.toJson(prod)
jsonStr: play.api.libs.json.JsValue = {"ean":5018206244611,"name":"Zebra Paperclips","description":"Zebra Length 28mm Assorted 150 Pack"}

What am I missing?


Additional note:

My implicit Writes is incomplete, I deliberately removed the last 2 fields (purchasePrice and sellingPrice). The reason: this code doesn't compile:

implicit object ProductWrites extends Writes[Product] {
  def writes(p: Product) = Json.obj(
    "ean" -> Json.toJson(p.ean),
    "name" -> Json.toJson(p.name),
    "description" -> Json.toJson(p.description),
    "purchase_price" -> p.purchasePrice.getOrElse(None),
    "selling_price" -> p.sellingPrice.getOrElse(None)
  )

}

I gives this error:

<console>:24: error: No Json serializer found for type Serializable. Try to implement an implicit Writes or Format for this type.
       "purchase_price" -> Json.toJson(p.purchasePrice.getOrElse(None)),

Thanks in advance, Raka


Solution

  • Your code has two problems:

    1. prod is of type Some[Product]. Use val prod: Option[Product] = ...

    2. You have to use the Option-Writes: Json.toJson(prod)(Writes.OptionWrites(adminProductWrites)). If you use implicit Writes then the code product by the compiler is exactly the same (uses OptionWrites too).

    
        scala> val prod: Option[Product] = Some(Product(5018206244611L, "Zebra Paperclips", "Zebra Length 28mm Assorted 150 Pack"))
            prod: Option[Product] = Some(Product(5018206244611,Zebra Paperclips,Zebra Length 28mm Assorted 150 Pack,None,None))
    
        scala> Json.toJson(prod)(Writes.OptionWrites(adminProductWrites))
            res2: play.api.libs.json.JsValue = {"ean":5018206244611,"name":"Zebra Paperclips","description":"Zebra Length 28mm Assorted 150 Pack"}
    
        scala> val prod: Option[Product] = None
            prod: Option[Product] = None
    
        scala> Json.toJson(prod)(Writes.OptionWrites(adminProductWrites))
            res3: play.api.libs.json.JsValue = null