播放JSON:读取可选的嵌套属性

3 json scala combinators playframework

我有以下case类和JSON组合器:

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)
    }
Run Code Online (Sandbox Code Playgroud)

我的JSON读取combinator Build将处理传入的JSON,例如:

{
    "buildType": {
        "projectName": "foo",
        "name": "bar"
    },
    "artifact-dependencies": {
        "build": [{
            "number": "1"
        }]
    },
    "changes": {
        "change": [{
            "verison": "1",
            "username": "bob",
            "comment": "foo"
        }]
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,如果artifact-dependencies缺少,它将会失败.我希望这是可选的.

我应该用readNullable吗?我试图这样做,但这失败了,因为它是一个嵌套属性.

这看起来是否实用,或者我是否滥用JSON组合器将我的JSON解析为案例类?

Mic*_*jac 5

目前Format[Commit],其伴随对象未被使用.我们没有理由不能使用简单的组合器,并将逻辑分开.

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

object Commit {

    implicit val reads: Reads[Commit] = (
        (__ \ "version").read[String] and 
        (__ \ "username").read[String] and 
        (__ \ "comment").read[String]
    )(Commit.apply _)

}
Run Code Online (Sandbox Code Playgroud)

然后,如果"artifact-dependencies"可能会缺失,我们应该让parentNumber一个Option[String]Build.

 case class Build(projectName: String, parentNumber: Option[String], commits: List[Commit])
Run Code Online (Sandbox Code Playgroud)

Reads将项目名称组合成一个单独的项目,以使Reads[Build]外观更加干净.

val nameReads: Reads[String] = for {
    projectName <- (__ \ "projectName").read[String]
    name <- (__ \ "name").read[String]
} yield s"$projectName::$name"
Run Code Online (Sandbox Code Playgroud)

然后,对于"artifact-dependencies"缺失的时间,我们可以使用orElseReads.pure(None)填充它None当整个分支(或子分支)不存在.在这种情况下,这比映射每个步骤更简单.

implicit val buildReads: Reads[Build] = (
    (__ \ "buildType").read[String](nameReads) and
    ((__ \ "artifact-dependencies" \ "build")(0) \ "number").readNullable[String].orElse(Reads.pure(None)) and
    (__ \ "changes" \ "change").read[List[Commit]]
)(Build.apply _)

val js2 = Json.parse("""
{
    "buildType": {
        "projectName": "foo",
        "name": "bar"
    },
    "changes": {
        "change": [{
            "version": "1",
            "username": "bob",
            "comment": "foo"
        }]
    }
}
""")

scala> js2.validate[Build]
res6: play.api.libs.json.JsResult[Build] = JsSuccess(Build(foo::bar,None,List(Commit(1,bob,foo))),)
Run Code Online (Sandbox Code Playgroud)