If statements within Play/Scala JSON parsing?
Asked Answered
V

1

8

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?

Vogt answered 14/5, 2015 at 20:6 Comment(0)
C
10

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())))))
Clodhopping answered 14/5, 2015 at 20:26 Comment(1)
This seems to work. I was able to replace my if/else statement with the orElse() functions: (JsPath \ "type1" \ "id").read[String].orElse((JsPath \ "type2" \ "id").read[String]).orElse((JsPath \ "type3" \ "id").read[String]) andVogt

© 2022 - 2024 — McMap. All rights reserved.