1
votes

Suppose I need to parse a JSON (see below).

{
  success: true
  statusCode: 0
  statusMessage: "Ok"
  payload { ... } // some actual data
}

First, I am parsing the "status" fields to get an instance of case class Status (see below)

case class Status(success: Boolean, code: Int, message: String)

val json = parse(text) // text is a JSON above

val statusList = for {
    JObject(obj) <- json
    JField("success", JBool(success)) <- obj
    JField("code", JInt(code)) <- obj
    JField("message", JString(message)) <- obj
  } yield Status(success, code, message)

Does it make sense ?

The type of statusList is List[Status]. It contains a single Status item. If the parsing fails the statusList is empty. I don't like it since I want Option[Status] rather than List[Status]

I can easily convert List[Status] to Option[Status] with headOption but I would like to get Option[Status] directly.

Can I parse the JSON with json4s to get Option[Status] without getting List[Status] first ?

3
What in particular doesn't work right with headOption that would work right if you could "get Option[Status] directly"? I don't understand from your question.Dan Getz
headOption does work. It just looks awkward to get List first and then convert to Option.Michael
Awkward in terms of code formatting or performance?Dan Getz
I don't care about performance too much here.Michael

3 Answers

2
votes

You can use json4s built-in extraction. In your case extractOpt instead of extract.

2
votes

You can use the XPath-like functions, together with toOption:

val statusOpt = for {
    JBool(success) <- (json / "success").toOption
    JInt(code) <- (json / "code").toOption
    JString(message) <- (json / "message").toOption
  } yield Status(success, code, message)
1
votes

An alternative to a for-comprehension, is the following class which implicitely adds an unpack method to a JValue:

case class UnpackableJValue(jv: JValue) {

  import scala.util.Try

  def unpack[A](f: JValue => A): A = f(jv)

  def unpackList[A](f: JValue => A): List[A] = jv match {
    case JArray(values) => values map f
    case _ => List.empty
  }

  def unpackOpt[A](f: JValue => A): Option[A] = Try(f(jv)).toOption

}

object UnpackableJValue {
  implicit def jvalue2unpackable(jv: JValue) = UnpackableJValue(jv)
}

The unpack method receives a function which is responsible to create a domain value from a JValue.

import UnpackableJValue._

implicit val formats = DefaultFormats

case class Status(success: Boolean, code: Int, message: String)

val json = parse(
  """
    |{
    |  "success": true,
    |  "statusCode": 0,
    |  "statusMessage": "Ok",
    |  "payload": { }
    |}
  """.stripMargin)

val res = json.unpack[Status] { v =>
  val success = (v \ "success").extract[Boolean]
  val code = (v \ "statusCode").extract[Int]
  val message = (v \ "statusMessage").extract[String]

  Status(success, code, message)
}

println(res)
// Status(true,0,Ok)