Search code examples
jsonscalarestplayframeworkplayframework-2.2

Scala - How to implement implicit JSON reader


I've implemented implicit Json Reads in order to read two fields from JSON, saleId and saleType. I wanted to make getSales method return a tuple (Int, String) representing saleId and saleType accordingly. But when I call the getSales method I'm getting the following errors:

Error:(46, 79) No JSON deserializer found for type (Int, String). Try to implement an implicit Reader or JsonFormat for this type.
    val salesData = (salesJson \ "sales").as[(Int, String)]

Error:(46, 79) not enough arguments for method as: (implicit reader: org.json4s.Reader[(Int, String)], implicit mf: Manifest[(Int, String)])(Int, String).
Unspecified value parameters reader, mf.
    val salesData = (salesJson \ "sales").as[(Int, String)]

I have implemented implicit Json Reads so really confused with the first error. Here is my implementation:

def getsales(context: SparkContext, saleId: Int): (Int, String)= {
    val url= buildUrl(context, saleId)

    implicit val salesReader: Reads[(Int, String)] = (
      (__ \ "id_from_API").read[Int] and
        (__ \ "sale_type").read[String]
      ).tupled

    val salesJson: JValue = parse(httpStringResponse(url, context))
    val salesData = (salesJson \ "sales_stats").as[(Int, String)]

    salesData
}

Solution

  • Two notes concerning you code:

    val salesData = (salesJson \ "sales").as[(Int, String)]
    val salesData = (salesJson \ "sales_stats").as[(Int, String)]
    

    might have to be the same.

    Instead of JValue you might have wanted to put JsValue in the line

    val salesJson: JValue = parse(httpStringResponse(url, context))
    

    Other than that testing your JSON reader code separately from the rest of your code might be helpful.

    The following worked for me:

    import org.scalatest.WordSpec
    import play.api.libs.functional.syntax._
    import play.api.libs.json._
    
    class ReadsExample extends WordSpec {
    
      "read" in {
        val sales =
          """
              {
                "sales_stats": {
                "id_from_API": 42,
                "sale_type": "cheap"
              }
            }
            """.stripMargin
    
         implicit val salesReader: Reads[(Int, String)] = (
          (JsPath \ "id_from_API").read[Int] and
            (JsPath \ "sale_type").read[String]
          ).tupled
    
        val salesJson: JsValue = Json.parse(sales)
        val salesData = (salesJson \ "sales_stats").as[(Int, String)]
     }
    
    }
    

    Please note that the version of play-json used here is 2.3.10.

    EDIT

    code example to the question in the comment

    import org.scalatest.WordSpec
    import play.api.libs.json.Json.reads
    import play.api.libs.json.{Json, _}
    
    class ReadsExample extends WordSpec {
    
      "read" in {
        val sales =
          """
              {
                "id_from_API": 9,
                "sale_type": {
                  "main" : "a",
                  "sub" : "b"
              }
          }
        """.stripMargin
    
        val salesJson: JsValue = Json.parse(sales)
        val salesData = salesJson.as[Sales]
      }
    
    }
    
    case class Sales(id_from_API: Int, sale_type: SaleType)
    case class SaleType(main: String, sub: String)
    
    object Sales {
      implicit val st: Reads[SaleType] = reads[SaleType]
      implicit val of: Reads[Sales] = reads[Sales]
    }