Parse a property that can be string or int

(Davide Icardi) #1

I’m new to Play Framework and Scala. I have a json like this:

	"data" : [
			"key" : "key1",
			"value" : "value 1"
			"key" : "key2",
			"value" : 2

As you can see the value property can be a String or an Int.
What is the best way to parse it?

My first idea was to create a case class with an Any type for the value property:

case class MyData(key: String, value: Any)

But I don’t know how to parse it. I have tried with:

implicit val dataReads: Reads[MyData] = (
  (JsPath \ "key").read[String] and
    (JsPath \ "value").read[Any]
  )(MyData.apply _)

But I get a compile error:

No Json deserializer found for type Any. Try to implement an implicit Reads or Format for this type.

Any idea?

1 Like
(Jules Ivanic) #2

Maybe you can implement it with an Either ? Either[Int, String]

(Davide Icardi) #3

Thanks Jules,

Using Either is for sure better than using Any on my case class. But I’m always stuck on parser side.
If I write:

(JsPath \ "value").read[Either[String, Int]]

I always get a similar error:

No Json deserializer found for type Either[String, Int]. Try to implement an implicit Reads or Format for this type.

I suppose that Either is not natively supported, that is fine for me. But I don’t understand how to insert some custom logic when using Reads. Or should I parse the object in another way?


(Jules Ivanic) #4

You need to implement at least this:

implicit def eitherReads[L: Reads, R: Reads]: Reads[Either[L, R]] = ???

You’re not the first one needing this. For example, there’s a solution proposed here (I didn’t read it, I’m on a phone. It’s not practical):

1 Like
(Schmitt Christian) #5

Well you are probably looking for something like that:

  sealed trait MyValue
  case class MyString(s: String) extends MyValue
  case class MyInt(i: Int) extends MyValue
  implicit val myValueReads: Reads[MyValue] = new Reads[MyValue] {
    override def reads(json: JsValue): JsResult[MyValue] = {
      json match {
        case JsString(s) => JsSuccess(MyString(s))
        case other => Reads.IntReads.reads(other).map(MyInt)
(Davide Icardi) #6

Thanks very much @schmitch! It works! Here the full code for future reference:

sealed trait DataValue
case class DataValueString(s: String) extends DataValue
case class DataValueInt(i: Int) extends DataValue

case class MyData(key: String, value: DataValue)

private implicit val dataValueReads: Reads[DataValue] = new Reads[DataValue] {
  override def reads(json: JsValue): JsResult[DataValue] = {
    json match {
      case JsString(s) => JsSuccess(DataValueString(s))
      case JsNumber(s) => JsSuccess(DataValueInt(s.toInt))
      case _ => throw new Exception("Invalid json value") // TODO Add better error handling

implicit val dataReads: Reads[MyData] = (
  (JsPath \ "key").read[String] and
    (JsPath \ "value").read[DataValue]
  )(MyData.apply _)
(Vasily) #7

Not quite the same, but may be useful in cases where number may or may not be quoted in json output:

import play.api.libs.json._
import play.api.libs.json.Reads._
import scala.util.{Failure, Success, Try}

// This allows parsing quoted numeric values in json output, e.g. correctly parsing {rows:"100"}
type Quoted[A] = A

def jsonQuotedReads[A](conv: String => A)(implicit r: Reads[A]): Reads[Quoted[A]] =
    new Reads[Quoted[A]] {
        def reads(json: JsValue): JsResult[A] =
        Try {
        } orElse Try {
        } match {
          case Success(value)     => JsSuccess(value)
          case Failure(exception) => JsError(exception.toString)

  def stringToInt(a: String): Int                     = a.toInt
  implicit val jsonQuotedReadsInt: Reads[Quoted[Int]] = jsonQuotedReads[Int](stringToInt)