【问题标题】:Play Json - Complex object creationPlay Json - 复杂对象创建
【发布时间】:2017-01-11 06:43:44
【问题描述】:

我正在尝试在我的 Play 框架应用程序 (Scala) 中创建 Json 阅读器。问题是,我的 Json 的一部分有点古怪,需要进一步处理以检索值。例如:

{  
  "field1":"value1",
  "field2":"value/1",
  "num2":2
}

带有案例类:

case class Field1(text: String, fields: Field2)
case class Field2(text: String, num: Int, num2: Int)

基本上Field2textnum 字段是通过拆分文本从值value/1 派生的。这是拆分器功能:

def splitter(path: String, num2: Int): Field2 = {
  val split = path.split("\\")
  Field2(split(0), split(1).toInt, num2)
}

这相当简单,实际的分离器功能要复杂得多。基本上,构造这个对象Field2 的唯一方法是将单个字符串传递给一个函数,该函数会吐出所需的对象。

如何为Field2(以及Field1 的扩展)创建一个阅读器?

这是我目前所拥有的:

object Field1 {
    implicit val reader = (
        (__ \ "field1").read[String] and
        (__).read[Field2]
    ) (Field1.apply _)
}

object Field2 {
    implicit val reader = (
        splitter((__ \ "field2").read[String], (__ \ "num2"))
    ) // Obviously incorrect syntax + type mismatch, but this is roughly what I'm trying to accomplish.
}

【问题讨论】:

    标签: json scala playframework playframework-2.0


    【解决方案1】:

    如果你使用非功能组合符语法,我认为它会变得更简单:

    object Field2 {
      implicit val reader = Reads[Field2] { json =>
        for {
          path <- (json \ "field2").validate[String]
          num2 <- (json \ "num2").validate[Int]
        } yield splitter(path, num2)
      }
    }
    

    此外,如果您希望拆分器进一步验证输入,请使其返回 JsResult[Field2],如下所示:

    def splitter(path: String, num2: Int): JsResult[Field2] = {
      val split = path.split("\\")
      if (split.size != 2) {
        JsError(s"$path must be of the form: {field}\\{num}")
      } else {
        Try(Field2(split(0), split(1).toInt, num2)).map(JsSuccess(_)).getOrElse {
          JsError(s"${split(1)} is not a valid Int")
        }
      }
    }
    

    然后修改阅读器:

    object Field2 {
      implicit val reader = Reads[Field2] { json =>
        for {
          path <- (json \ "field2").validate[String]
          num2 <- (json \ "num2").validate[Int]
          field2 <- splitter(path, num2)
        } yield field2
      }
    }
    

    如果您仍然喜欢使用函数式语法并且不介意拆分器确实缺少验证,请尝试以下操作:

    def splitter(path: String, num2: Int): Field2 = {
      val split = path.split("\\")
      Field2(split(0), split(1).toInt, num2)
    }
    
    implicit val reader = (
      (__ \ "field2").read[String] and
      (__ \ "num2").read[Int]
    )(splitter _)
    

    我建议不要这样做,它不安全(split(1)toInt 都可能引发异常)并且函数式语法可能存在性能问题。见https://www.lucidchart.com/techblog/2016/08/29/speeding-up-restful-services-in-play-framework/

    【讨论】:

    • 这很好用。但是有没有办法使用常规的函数式语法?我在我的代码的其他部分中使用了它,我希望保持一致。
    • @Jeff 函数式语法的问题是您不能引用以前的字段来计算事物。调用 apply 后,您可以修改生成的 Reader,但您需要一个中间类型来跟踪您计算的数据。此外,在某些情况下,您可能会遇到函数式语法的性能问题:lucidchart.com/techblog/2016/08/29/…
    • @Jeff 我添加了一个使用函数式语法的示例。
    • 知道了。非常感谢!
    【解决方案2】:

    我不知道你为什么需要案例类,但你也可以根据需要转换 json

     (__ \ "field2" \ "num2").json.copyFrom((__ \ "num2").json.pick) and
        (__ \ "field2").json.update(
          of[String].map { o =>
            val split = o.split("/")
            Json.obj(
              "text" -> split(0),
              "num" -> split(1).toInt
            )
          }
        )
      ).reduce andThen (__ \ "num2").json.prune
    
    scala> j: play.api.libs.json.JsResult[play.api.libs.json.JsObject] = JsSuccess({"field2":{"num2":2,"text":"value","num":1},"field1":"value1"},/num2)
    

    然后你就可以使用你的Reads[Field1]

    【讨论】:

    • 案例类是已经设计好的系统的一个组成部分。此外,我提供的代码是实际代码的一个小近似值,它们将沿着相同的路线,但要大得多,因此每次转换 json 似乎都不可行。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多