Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

If statements within Play/Scala JSON parsing?

Is there a way to perform conditional logic while parsing json using Scala/Play?

For example, I would like to do something like the following:

implicit val playlistItemInfo: Reads[PlaylistItemInfo] = (
    (if(( (JsPath \ "type1").readNullable[String]) != null){ (JsPath \ "type1" \ "id").read[String]} else {(JsPath \ "type2" \ "id").read[String]}) and
      (JsPath \ "name").readNullable[String]
    )(PlaylistItemInfo.apply _)

In my hypothetical JSON parsing example, there are two possible ways to parse the JSON. If the item is of "type1", then there will be a value for "type1" in the JSON. If this is not present in the JSON or its value is null/empty, then I would like to read the JSON node "type2" instead.

The above example does not work, but it gives you the idea of what I am trying to do.

Is this possible?

like image 843
jaxim Avatar asked May 14 '15 20:05

jaxim


People also ask

How to parse JSON string in Scala?

We can use a well-known Lift-JSON library to parse the JSON string. This library contains many methods which could be used to deserialize the JSON string into Scala objects. In the above code, we have created a case class Employee to match the JSON data.

How to parse a JSON string with array of objects?

So the objective here is that given a JSON string with an array of objects, we want to deserialize it into Scala objects so we can use it in our application. We can parse the JSON using the plain Scala methods and features or use different APIs and libraries to parse JSON files like Lift-JSON library and Circe.

What is a format Implicit object in Scala?

Like Reads and Writes, we usually write Format as implicit objects. When our program finds that Format implicit object in scope, our program will encode that Scala type into a JSON or decode a JSON into a Scala type. As Format is a mix of Reads and Writes, we have defined our format implicit object by using both apply and unapply functions.


1 Answers

The proper way to do this with JSON combinators is to use orElse. Each piece of the combinator must be a Reads[YourType], so if/else doesn't quite work because your if clause doesn't return a Boolean, it returns Reads[PlaylistItemInfo] checked against null which will always be true. orElse let's us combine one Reads that looks for the type1 field, and a second one that looks for the type2 field as a fallback.

This might not follow your exact structure, but here's the idea:

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

case class PlaylistItemInfo(id: Option[String], tpe: String)

object PlaylistItemInfo {
    implicit val reads: Reads[PlaylistItemInfo] = (
        (__ \ "id").readNullable[String] and
        (__ \ "type1").read[String].orElse((__ \ "type2").read[String])
    )(PlaylistItemInfo.apply _)
}

// Read type 1 over type 2
val js = Json.parse("""{"id": "test", "type1": "111", "type2": "2222"}""")

scala> js.validate[PlaylistItemInfo]
res1: play.api.libs.json.JsResult[PlaylistItemInfo] = JsSuccess(PlaylistItemInfo(Some(test),111),)

// Read type 2 when type 1 is unavailable
val js = Json.parse("""{"id": "test", "type2": "22222"}""")

scala> js.validate[PlaylistItemInfo]
res2: play.api.libs.json.JsResult[PlaylistItemInfo] = JsSuccess(PlaylistItemInfo(Some(test),22222),)

// Error from neither
val js = Json.parse("""{"id": "test", "type100": "fake"}""")

scala> js.validate[PlaylistItemInfo]
res3: play.api.libs.json.JsResult[PlaylistItemInfo] = JsError(List((/type2,List(ValidationError(error.path.missing,WrappedArray())))))
like image 92
Michael Zajac Avatar answered Nov 03 '22 01:11

Michael Zajac