Play JSON: reading optional nested properties

I have the following case classes and JSON combinators:

case class Commit(
    sha: String,
    username: String,
    message: String
)

object Commit {
    implicit val format = Json.format[Commit]
}

case class Build(
    projectName: String,
    parentNumber: String,
    commits: List[Commit]
)

val buildReads: Reads[Build] =
    for {
        projectName <- (__  "buildType"  "projectName").read[String]
        name <- (__  "buildType"  "name").read[String]
        parentNumber <- ((__  "artifact-dependencies"  "build")(0)  "number").read[String]
        changes <- (__  "changes"  "change").read[List[Map[String, String]]]
    } yield {
        val commits = for {
            change <- changes
            sha <- change.get("version")
            username <- change.get("username")
            comment <- change.get("comment")
        } yield Commit(sha, username, comment)
        Build(s"$projectName::$name", parentNumber, commits)
    }

My JSON reads combinator for Build will handle incoming JSON such as:

{
    "buildType": {
        "projectName": "foo",
        "name": "bar"
    },
    "artifact-dependencies": {
        "build": [{
            "number": "1"
        }]
    },
    "changes": {
        "change": [{
            "verison": "1",
            "username": "bob",
            "comment": "foo"
        }]
    }
}

However, if artifact-dependencies is missing, it will fall over. I would like this to be optional.

Should I use readNullable? I have tried to do so, but this fails because it is a nested property.

Does this look pragmatic, or am I abusing JSON combinators to parse my JSON into a case class?


Source: json

Leave a Reply