0
votes

I have following case class

case class DataResponse(results: Iterable[Array[Option[String]]], exceptionMessage: Option[String])

I can't seem to be able to write Reads for this class mainly because of type of results. If I try without option. i.e. Iterable[Array[String]] it works but then it blows up when json string has null values.

implicit val DataReads2 = ( (JsPath \ “results”).read[Iterable[Array[Option[String]]]] and //compile error (JsPath \ “exceptionMessage”).readNullable[String] )(DataResponse.apply _)

compile error:

No Json deserializer found for type Iterable[Array[Option[String]]]. Try to implement an implicit Reads or Format for this type.

If I try to implement implicit Reads for that I get:

implicit val itrOptReads = Json.reads[Iterable[Array[Option[String]]]]

No apply function found for scala.collection.Iterable

basically, I can't seem to find a simpler way to handle unmarsheling json to Iterable[Array[Option[String]]] . there should be one.

2

2 Answers

1
votes

You can implement a CustomIterableFormat in this case. Here's the example:

object CustomIterableFormat extends Reads[Iterable[Array[Option[String]]]] with Writes[Iterable[Array[Option[String]]]] {
  implicit val optionalStringArrayFormat = CustomArrayFormat
  override def reads(json: JsValue): JsResult[Iterable[Array[Option[String]]]] = {
    Try(json.as[Iterable[JsValue]].map { someJson =>
      someJson.as[Array[Option[String]]]
    }).map(JsSuccess(_)).getOrElse(JsError(s"invalid sequence: $json"))
  }

  override def writes(o: Iterable[Array[Option[String]]]): JsValue = {
    Json.toJson(o)
  }
}

object CustomArrayFormat extends Reads[Array[Option[String]]] with Writes[Array[Option[String]]] {
  override def reads(json: JsValue): JsResult[Array[Option[String]]] = {
    Try(json.as[Array[JsValue]].map {
      case JsNull          => None
      case JsString(value) => Some(value)
    }).map(JsSuccess(_)).getOrElse(JsError(s"invalid sequence: $json"))
  }

  override def writes(o: Array[Option[String]]): JsValue = {
    Json.toJson(o)
  }
}

And then just import the CustomIterableFormat in your companion object:

case class DataResponse(results: Iterable[Array[Option[String]]], exceptionMessage: Option[String])

object DataResponse {
  implicit val resultsFormat = CustomIterableFormat

  implicit val DataReads2 = (
    (JsPath \ "results").read[Iterable[Array[Option[String]]]] and
      (JsPath \ "exceptionMessage").readNullable[String]
  )(DataResponse.apply _)

}

Here's a simple working example:

object TestDataResponseParsing {
  def main(args: Array[String]): Unit = {

    val jsonStr = """{"results":[[null,"testing"]],"exceptionMessage":null}"""
    val parsedFromJson = Json.parse(jsonStr).as[DataResponse]
    println(parsedFromJson)
  }
}

Hope this helps!

1
votes

Circe is one of the most popular JSON parsing library in Scala

Here's a more elegant solution using Circe.

import io.circe.generic.auto._
import io.circe.parser

case class DataResponse(results: Iterable[Array[Option[String]]], exceptionMessage: Option[String])

object DataResponseParsing extends App {

  val jsonStr = """{
                  "results" : [
                      [
                        null,
                        "bar-1"
                      ],
                      [
                         "foo-2",
                         "bar-2"
                       ]
                    ],
                    "exceptionMessage" : null
                  }""".stripMargin

  val result = parser.decode[DataResponse](jsonStr)

  val dataResponse = result match {
    case Right(value) => value
    case Left(error) => throw error
  }
  println(dataResponse)
}

Circe's automatic derivation is very well versed and it automatically encode/decode almost all types of Scala objects. So we don't need to define implicit anywhere. Just import io.circe.generic.auto._ in your Scala class and that's it.

Here's a good reference blog for parsing JSON using Circe

Hope this helps!!