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

时间:2015-08-02 07:42:24

标签: json scala playframework combinators

我有以下案例类和JSON组合器:

background.jpg

我的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) } 的组合器将处理传入的JSON,例如:

Build

但是,如果缺少{ "buildType": { "projectName": "foo", "name": "bar" }, "artifact-dependencies": { "build": [{ "number": "1" }] }, "changes": { "change": [{ "verison": "1", "username": "bob", "comment": "foo" }] } } ,则会失败。我希望这是可选的。

我应该使用artifact-dependencies吗?我试图这样做,但这失败了,因为它是一个嵌套属性。

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

2 个答案:

答案 0 :(得分: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 _)

}

然后,如果"artifact-dependencies"可能丢失,我们应该在parentNumber

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

我将项目名称组合成一个单独的Reads[Build],以使val nameReads: Reads[String] = for { projectName <- (__ \ "projectName").read[String] name <- (__ \ "name").read[String] } yield s"$projectName::$name" 看起来更干净。

"artifact-dependencies"

然后,对于缺少orElse的时候,当整个分支(或子分支)不是Reads.pure(None)时,我们可以使用Noneimplicit 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))),) 填充$scope.addRow = function() { $scope.rows.push(angular.copy($scope.rows[0])); } 那里。在这种情况下,这比映射每个步骤更简单。

str_replace

答案 1 :(得分:0)

我尽量让我的格式与json尽可能匹配。不可否认,在这种情况下,它有点尴尬,但那是因为json架构有点奇怪。鉴于这些限制,我将如何做到这一点:

import play.api.libs.functional.syntax._
import play.api.libs.json._
case class Build(buildType: BuildType, `artifact-dependencies`: Option[ArtifactDependencies], changes: Changes)
case class BuildType(projectName: String, name: String)
case class ArtifactDependencies(build: List[DependencyInfo])
case class DependencyInfo(number: String)
case class Changes(change: List[Commit])
case class Commit(version: String, username: String, comment: String)

object BuildType {
  implicit val buildTypeReads: Reads[BuildType] = (
    (JsPath \ "projectName").read[String] and
    (JsPath \ "name").read[String]
  )(BuildType.apply _)

}

object ArtifactDependencies {
  implicit val artifactDependencyReads: Reads[ArtifactDependencies] =
    (JsPath \ "build").read[List[DependencyInfo]].map(ArtifactDependencies.apply)
}

object DependencyInfo {
  implicit val dependencyInfoReads: Reads[DependencyInfo] =
    (JsPath \ "number").read[String].map(DependencyInfo.apply)

}

object Changes {
  implicit val changesReads: Reads[Changes] =
    (JsPath \ "change").read[List[Commit]].map(Changes.apply)
}

object Commit {
  implicit val commitReads: Reads[Commit] = (
    (JsPath \ "version").read[String] and
    (JsPath \ "username").read[String] and
    (JsPath \ "comment").read[String]
  )(Commit.apply _)
}
object Build {

  implicit val buildReads: Reads[Build] = (
    (JsPath \ "buildType").read[BuildType] and
    (JsPath \ "artifact-dependencies").readNullable[ArtifactDependencies] and
    (JsPath \ "changes").read[Changes]
  )(Build.apply _)

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

    println(js.validate[Build])

    val js1 = Json.parse(
      """
        |{
        |    "buildType": {
        |        "projectName": "foo",
        |        "name": "bar"
        |    },
        |    "artifact-dependencies": {
        |        "build": [{
        |            "number": "1"
        |        }]
        |    },
        |    "changes": {
        |        "change": [{
        |            "version": "1",
        |            "username": "bob",
        |            "comment": "foo"
        |        }]
        |    }
        |}
      """.stripMargin)

    println(js1.validate[Build])
  }
}

输出结果为:

[info] JsSuccess(Build(BuildType(foo,bar),None,Changes(List(Commit(1,bob,foo)))),)
[info] JsSuccess(Build(BuildType(foo,bar),Some(ArtifactDependencies(List(DependencyInfo(1)))),Changes(List(Commit(1,bob,foo)))),)

注意稍微尴尬

(JsPath \ "change").read[List[Commit]].map(Changes.apply)
单个参数案例类需要

编辑:

我错过的关键部分是parentNumber现在成为Build上定义的方法,如下所示:

case class Build(buildType: BuildType, `artifact-dependencies`: Option[ArtifactDependencies], changes: Changes) {
  def parentNumber: Option[String] = `artifact-dependencies`.flatMap(_.build.headOption.map(_.number))
}