2

I cannot wrap my head around why it throws an error on the following line:

val anyReads = Reads[Any](m => metaValueToJsValue(m))

Error Message: type mismatch; found : play.api.libs.json.JsValue required: play.api.libs.json.JsResult[Any] Note: implicit value readsMap is not applicable here because it comes after the application point and it lacks an explicit result type

I've pasted my code below. Any help is appreciated! Thanks!

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

case class TempClass(
  metaValue: Option[Map[String, Any]])

object TempClass {

  val anyReads = Reads[Any](m => metaValueToJsValue(m))

  def metaValueToJsValue(m: Any): JsValue = {
    m match {
      case s: String => JsString(s)
      case n: Int => JsNumber(n): JsValue
      case n: Long => JsNumber(n): JsValue
      case n: Double => JsNumber(n): JsValue
      case n: BigDecimal => JsNumber(n): JsValue
      case b: Boolean => JsBoolean(b)
      case l: Seq[Any] => JsArray(l.map(metaValueToJsValue)): JsValue
    }
  }

  implicit val readsMap = Reads[Map[String, Any]](m => Reads.mapReads[Any](anyReads).reads(m))
  implicit val reads = Json.reads[TempClass]
}
Paweł Prażak
  • 3,091
  • 1
  • 27
  • 42
slizorn
  • 315
  • 1
  • 4
  • 14
  • You need to ascribe the type to your implicit val. Refer http://stackoverflow.com/questions/2731185/why-does-this-explicit-call-of-a-scala-method-allow-it-to-be-implicitly-resolved/2731285#2731285 – Ravi Kiran Sep 09 '14 at 10:02
  • implicit val readsMap: Reads[Map[String, Any]] = Reads[Map[String, Any]](m => Reads.mapReads[Any](anyReads).reads(m)) implicit val reads: Reads[TempClass] = Json.reads[TempClass] The above has no effect though :/ Am I doing it wrong? – slizorn Sep 09 '14 at 10:15

2 Answers2

3

Looks like you are doing something really wrong here.

val anyReads = Reads[Any](m => metaValueToJsValue(m))

Reads takes as argument a function which accepts JsValue and returns JsResult[A] but you are passing a function which takes Any and return JsValue.

So the signature of def metaValueToJsValue(m: Any): JsValue = { should be like this

def metaValueToJsValue(m: JsValue): JsResult[Any] = {

NOTE: Generally frameworks doesn't provide default serializers for Polymorphic Maps (Map[String, Any]) because the Any can again be Maps/Lists and can go to any level. So application should have knowledge of the incoming JSON structure. In the below code i have made an assumption of one level ie, Json attribute values themselves can't be Json objects/arrays

Please refer to the code below:

object TempClass {

    implicit val readsMap = Reads[Map[String, Any]](m => Reads.mapReads[Any](anyReads).reads(m))
    implicit val reads = Json.reads[TempClass]
    val anyReads = Reads[Any](m => metaValueToJsValue(m))

    def metaValueToJsValue(m: JsValue): JsResult[Any] = {
      m match {
        case JsObject(m) => {
          val m1 = m.map(f => (f._1, convert(f._2))).toMap
          JsSuccess(m1)
        }
        case JsString(s) => JsSuccess(s)
        case JsNumber(n) => JsSuccess(n)
        case JsBoolean(b) => JsSuccess(b)
        case JsArray(arr) => {
          val list = arr.map(convert)
          JsSuccess(list)
        }
      }
    }

    def convert(m: JsValue): Any = {
      m match {
        case JsString(s) => s
        case JsNumber(n) => n
        case JsBoolean(b) => b
      }
    }

    def main(args: Array[String]) {
      val json = """{"metaValue" : {"name":"John", "age":30}}"""
      val tmpClass = Json.parse(json).as[TempClass]
      println(tmpClass)
    }

  }
Vishal John
  • 4,231
  • 25
  • 41
  • Sorry but could you please explain the following to me? What is the purpose of the main method in the Object, also why am I no longer able to parse this class? val tempClass = Json.parse(response.body).as[TempClass] – slizorn Sep 09 '14 at 13:18
  • 1
    I added main method just to show how to parse TempClass from a json string. You can remove that method. Also you need to enhance this code by matching more cases like JsNull, JsUndefined etc in the methods. – Vishal John Sep 09 '14 at 13:24
1

I've found out that it's better to have the JsObject last otherwise everything can be matched as a JsObject. Also there isn't a need for the convert. A better solution (though incomplete) is as follows:

object ContractDetails {
  implicit val readsMap = Reads[Map[String, Any]](m => Reads.mapReads[Any](anyReads).reads(m))
  implicit val reads = Json.reads[ContractDetails]
  val anyReads = Reads[Any](m => metaValueToJsValue(m))

  def metaValueToJsValue(m: JsValue): JsResult[Any] = {
    m match {
      case JsBoolean(b) => JsSuccess(b)
      case JsNumber(n) => JsSuccess(n)
      case JsString(s) => JsSuccess(s)
      case JsArray(arr) => {
        val list = arr.map(metaValueToJsValue)
        JsSuccess(list)
      }
      case JsNull => JsSuccess(null)
      //case x => JsFailure(x.toString())
      case JsObject(m) => {
        val m1 = m.map(f => (f._1, metaValueToJsValue(f._2))).toMap
        JsSuccess(m1)
      }
    }
  }
slizorn
  • 315
  • 1
  • 4
  • 14