gpt4 book ai didi

json - 如何在 Scala Play 中使用可变键解析 JSON?

转载 作者:行者123 更新时间:2023-12-04 10:24:44 26 4
gpt4 key购买 nike

新年快乐,首先!

我在 Play 中解析 JSON 时遇到了一些问题,我正在处理的格式如下:

JSON Response:

...
"image":{
"large":{
"path":"http://url.jpg",
"width":300,
"height":200
},
"medium":{
"path":"http://url.jpg",
"width":200,
"height":133
},
...
}
...

我被尺寸困在了球场上。它们显然是变量,我不确定如何为此编写格式化程序? JSON 来自外部服务。

到目前为止我有
final case class Foo(
..
..
image: Option[Image])


final case class Image(size: List[Size])

final case class Size(path: String, width: Int, height: Int)

对于我刚刚做的格式化 Json.reads[x]对于所有类(class)。但是,我很确定 size 变量正在抛弃格式,因为它无法从传入的 JSON 创建 Image 对象。

最佳答案

更新 2016-07-28

下面描述的解决方案打破了 Referential Transparency因为使用了return关键字,不是我今天推荐的东西。尽管如此,我并没有因为历史原因而将其保留。

介绍

这里的问题是您需要找到某个地方来保存每个 Size 的 key 。 Image 中的对象目的。有两种方法可以做到这一点,一种是保存在Size中。对象本身。这是有道理的,因为该名称与 Size 密切相关。对象,并且可以方便地将其存储在那里。因此,让我们首先探索该解决方案。

关于对称性的快速说明

在我们深入研究任何解决方案之前,让我首先介绍对称的概念。这个想法是,当您读取任何 Json 值时,您可以使用 Scala 模型表示返回到完全相同的 Json 值。

处理编码数据时的对称性不是严格要求的,实际上有时它要么是不可能的,要么执行它会成本太高而没有任何实际 yield 。但通常它很容易实现,它使序列化实现的工作变得更好。在许多情况下,它也是必需的。

保存 nameSize

import play.api.libs.json.Format
import play.api.libs.json.JsPath
import play.api.libs.json.Reads
import play.api.libs.json.JsValue
import play.api.libs.json.JsResult
import play.api.libs.json.JsSuccess
import play.api.libs.json.JsError
import play.api.libs.json.JsObject
import play.api.libs.json.Json

final case class Foo(images: Option[Image])

object Foo {
implicit val fooFormat: Format[Foo] = Json.format[Foo]
}

final case class Image(sizes: Seq[Size])

object Image {

implicit val imagesFormat: Format[Image] =
new Format[Image] {

/** @inheritdoc */
override def reads(json: JsValue): JsResult[Image] = json match {
case j: JsObject => {
JsSuccess(Image(j.fields.map{
case (name, size: JsObject) =>
if(size.keys.size == 3){
val valueMap = size.value
valueMap.get("path").flatMap(_.asOpt[String]).flatMap(
p=> valueMap.get("height").flatMap(_.asOpt[Int]).flatMap(
h => valueMap.get("width").flatMap(_.asOpt[Int]).flatMap(
w => Some(Size(name, p, h, w))
))) match {
case Some(value) => value
case None => return JsError("Invalid input")
}
} else {
return JsError("Invalid keys on object")
}
case _ =>
return JsError("Invalid JSON Type")
}))
}
case _ => JsError("Invalid Image")
}

/** @inheritdoc */
override def writes(o: Image): JsValue = {
JsObject(o.sizes.map((s: Size) =>
(s.name ->
Json.obj(
("path" -> s.path),
("height" -> s.height),
("width" -> s.width)))))
}
}

}

final case class Size(name: String, path: String, height: Int, width: Int)

在此解决方案中 Size没有任何直接的 Json 序列化或反序列化,而是作为 Image 的产物而来。目的。这是因为,为了对您的 Image 进行对称序列化您需要保留的对象不仅是 Size 的参数对象、路径、高度和宽度,还有 nameSize指定为 Image 上的键目的。如果你不存储这个,你就不能自由地来回走动。

所以这就像我们在下面看到的那样,
scala> import play.api.libs.json.Json
import play.api.libs.json.Json

scala> Json.parse("""
| {
| "large":{
| "path":"http://url.jpg",
| "width":300,
| "height":200
| },
| "medium":{
| "path":"http://url.jpg",
| "width":200,
| "height":133
| }
| }""")
res0: play.api.libs.json.JsValue = {"large":{"path":"http://url.jpg","width":300,"height":200},"medium":{"path":"http://url.jpg","width":200,"height":133}}

scala> res0.validate[Image]
res1: play.api.libs.json.JsResult[Image] = JsSuccess(Image(ListBuffer(Size(large,http://url.jpg,200,300), Size(medium,http://url.jpg,133,200))),)

scala>

非常重要的是它既安全又对称
scala> Json.toJson(res0.validate[Image].get)
res4: play.api.libs.json.JsValue = {"large":{"path":"http://url.jpg","height":200,"width":300},"medium":{"path":"http://url.jpg","height":133,"width":200}}

scala>

关于安全的快速说明

在生产代码中,您永远、永远、永远不想使用 .as[T] JsValue 上的方法.这是因为如果数据不是你所期望的,它会在没有任何有意义的错误处理的情况下爆炸。如果必须,请使用 .asOpt[T] ,但一般来说更好的选择是 .validate[T] ,因为这会在失败时产生某种形式的错误,您可以记录该错误,然后向用户报告。

可能是更好的解决方案

现在,可能更好的方法是更改​​ Image案例类声明如下
final case class Image(s: Seq[(String, Size)])

然后保留 Size正如你最初拥有的那样
final case class Size(path: String, height: Int, width: Int)

然后您只需要执行以下操作即可确保安全和对称。

如果我们这样做,那么实现会变得更好,同时仍然是安全和对称的。
import play.api.libs.json.Format
import play.api.libs.json.JsPath
import play.api.libs.json.Reads
import play.api.libs.json.JsValue
import play.api.libs.json.JsResult
import play.api.libs.json.JsSuccess
import play.api.libs.json.JsError
import play.api.libs.json.JsObject
import play.api.libs.json.Json

final case class Foo(images: Option[Image])

object Foo {
implicit val fooFormat: Format[Foo] = Json.format[Foo]
}

final case class Image(sizes: Seq[(String, Size)])

object Image {

implicit val imagesFormat: Format[Image] =
new Format[Image] {

/** @inheritdoc */
override def reads(json: JsValue): JsResult[Image] = json match {
case j: JsObject =>
JsSuccess(Image(j.fields.map{
case (name, size) =>
size.validate[Size] match {
case JsSuccess(validSize, _) => (name, validSize)
case e: JsError => return e
}
}))
case _ =>
JsError("Invalid JSON type")
}

/** @inheritdoc */
override def writes(o: Image): JsValue = Json.toJson(o.sizes.toMap)
}
}

final case class Size(path: String, height: Int, width: Int)

object Size {
implicit val sizeFormat: Format[Size] = Json.format[Size]
}

仍然像以前一样工作
scala> Json.parse("""
| {
| "large":{
| "path":"http://url.jpg",
| "width":300,
| "height":200
| },
| "medium":{
| "path":"http://url.jpg",
| "width":200,
| "height":133}}""")
res1: play.api.libs.json.JsValue = {"large":{"path":"http://url.jpg","width":300,"height":200},"medium":{"path":"http://url.jpg","width":200,"height":133}}

scala> res1.validate[Image]
res2: play.api.libs.json.JsResult[Image] = JsSuccess(Image(ListBuffer((large,Size(http://url.jpg,200,300)), (medium,Size(http://url.jpg,133,200)))),)

scala> Json.toJson(res1.validate[Image].get)
res3: play.api.libs.json.JsValue = {"large":{"path":"http://url.jpg","height":200,"width":300},"medium":{"path":"http://url.jpg","height":133,"width":200}}

但好处是 Size现在反射(reflect)了真正的 Json,也就是说你可以序列化和反序列化 Size值。这使得工作和思考都更容易。

TL;DR 评论 reads在第一个例子中

尽管我认为第一个解决方案比第二个解决方案稍逊一筹,但我们确实在 reads 的第一个实现中使用了一些有趣的习语。从更一般的意义上讲,它们非常有用,但通常不太好理解。所以我想花时间为那些感兴趣的人更详细地介绍它们。如果您已经了解使用中的习语,或者您只是不在乎,请随时跳过此讨论。
flatMap链接

当我们试图从 valueMap 中获取我们需要的值时,在任何一个所有步骤都可能出错。我们希望在不引发灾难性异常的情况下合理处理这些情况。

为此,我们使用 Option值(value)与共同 flatMap函数来链接我们的计算。对于每个想要的值,我们实际上有两个步骤,从 valueMap 中获取值我们使用 asOpt[T] 将其强制为正确的类型功能。现在好消息是 valueMap.get(s: String)jsValue.asOpt[T]都返回 Option值。这意味着我们可以使用 flatMap构建我们的最终结果。 flatMap具有很好的属性,如果 flatMap 中的任何步骤链失败,即返回 None ,然后所有其他步骤都不会运行,最终结果返回为 None .

这个习语是通用 Monadic 编程的一部分,它在函数式语言中很常见,尤其是 Haskell 和 Scala。在 Scala 中,它并不经常被称为 Monadic,因为当这个概念在 Haskell 中被引入时,它经常被解释得很差,导致许多人不喜欢它,尽管它实际上非常有用。因此,人们常常害怕对 Scala 使用“M 字”。

功能性短路
reads 中使用的另一个习语,在两个版本中,通过使用 return 来短路函数调用scala 中的关键字。

您可能知道,使用 return在 Scala 中经常不鼓励使用关键字,因为任何函数的最终值都会自动成为函数的返回值。然而,有一个非常有用的时间可以使用 return关键字,即当您调用一个表示对某事重复调用的函数时,例如 map功能。如果您在其中一个输入上遇到某些终端条件,您可以使用 return停止执行 map 的关键字调用其余元素。这有点类似于使用 breakfor在 Java 等语言中循环。

在我们的例子中,我们想要确保 Json 中元素的某些事情,比如它具有正确的键和类型,如果在任何时候我们的任何假设不正确,我们想要返回正确的错误信息。现在我们可以 map检查 Json 中的字段,然后检查 map 之后的结果操作已完成,但请考虑是否有人向我们发送了非常大的 Json,其中包含数千个没有我们想要的结构的键。我们必须将我们的函数应用于 全部 即使我们知道在第一个应用程序之后出现错误,也可以使用这些值。使用 return我们可以结束 map应用程序一旦我们知道错误,而不必花时间应用程序 map当结果已知时,跨其余元素应用。

无论如何,我希望一点点迂腐的解释是有帮助的!

关于json - 如何在 Scala Play 中使用可变键解析 JSON?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27732552/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com