如何在Scala Play中使用变量键解析JSON?
新年快乐,首先!
我在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].但是我很确定大小的变量会抛弃格式化,因为它无法从JSON中创建一个Image对象. 解决方法
更新2016-07-28
由于使用了return关键字,下面描述的解决方案会破坏Referential Transparency并且不是我今天推荐的.尽管如此,由于历史原因,我不会因此而离开. 介绍 这里的问题是你需要找到一个位置来保存Image对象中每个Size对象的键.有两种方法可以做到这一点,一种是将它保存在Size对象本身中.这是有道理的,因为名称与Size对象密切相关,并且将其存储在那里很方便.所以我们先来探索一下这个解 关于对称性的快速说明 在我们深入研究任何解决方案之前,让我先介绍一下对称性的概念.这个想法是,当您读取任何Json值时,您可以使用Scala模型表示返回到完全相同的Json值. 在处理编组数据时的对称性并不是严格要求的,实际上有时候它是不可能的,或者强制执行它会成本太高而没有任何实际收益.但通常它很容易实现,它使得序列化实现更好.在许多情况下,它也是必需的. 在大小中保存名称 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对象,路径,高度和宽度的参数,还需要保留在Image对象上指定为Size的Size的名称. .如果你不存储它,你就不能自由地来回走动. 所以这就像我们在下面看到的那样, 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","height":200},"medium":{"path":"http://url.jpg","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,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},"height":133,"width":200}} scala> 关于安全的快速说明 在生产代码中,您永远不会永远不想在JsValue上使用.as [T]方法.这是因为如果数据不符合您的预期,它会在没有任何有意义的错误处理的情况下爆炸.如果必须,请使用.asOpt [T],但通常更好的选择是.validate [T],因为这会在失败时产生某种形式的错误,您可以记录然后向用户报告. 可能是一个更好的解决方案 现在,可能更好的方法是将Image case类声明更改为以下内容 final case class Image(s: Seq[(String,Size)]) 然后保持你原来的尺寸, final case class Size(path: String,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,width: Int) object Size { implicit val sizeFormat: Format[Size] = Json.format[Size] } 仍然像以前一样工作 scala> Json.parse(""" | { | "large":{ | "path":"http://url.jpg",| "height":133}}""") res1: play.api.libs.json.JsValue = {"large":{"path":"http://url.jpg","height":133}} scala> res1.validate[Image] res2: play.api.libs.json.JsResult[Image] = JsSuccess(Image(ListBuffer((large,Size(http://url.jpg,300)),(medium,200)))),) scala> Json.toJson(res1.validate[Image].get) res3: play.api.libs.json.JsValue = {"large":{"path":"http://url.jpg","width":200}} 但是有了这个好处,现在Size反映了真正的Json,那就是你可以序列化和反序列化Size值.这使得它更容易使用和思考. TL; DR评论第一个例子中的读数 虽然我认为第一个解决方案在某种程度上不如第二个解决方案,但我们确实在第一个读取实现中使用了一些有趣的习惯用法,这些习惯用法在更广泛的意义上非常有用,但通常不太清楚.所以我想花点时间详细介绍一下感兴趣的人.如果你已经理解了使用中的习语,或者你只是不在乎,请随意跳过这个讨论. flatMap链接 当我们尝试从valueMap中获取所需的值时,在任何步骤中都可能出错.我们希望合理地处理这些情况,而不会抛出灾难性的异常. 为了实现这一点,我们使用Option值和常用的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字”. 功能短路 在两个版本中,读取中使用的另一个习惯用法是通过在scala中使用return关键字来短路函数调用. 您可能知道,在Scala中通常不鼓励使用return关键字,因为任何函数的最终值都会自动变为函数的返回值.然而,有一个非常有用的时间来使用return关键字,也就是当你调用一个代表重复调用的函数时,例如map函数.如果您在其中一个输入上遇到某个终端条件,则可以使用return关键字停止对其余元素执行地图调用.这有点类似于在Java等语言中使用for循环中的break. 在我们的例子中,我们想要确保关于Json中元素的某些事情,比如它具有正确的键和类型,并且如果在任何时候我们的任何假设都不正确,我们想要返回正确的错误信息.现在我们可以映射Json中的字段,然后在地图操作完成后检查结果,但考虑是否有人向我们发送了非常大的Json,其中包含数千个没有我们想要的结构的键.即使我们知道在第一次应用之后我们有错误,我们也必须将我们的函数应用于所有值.使用return我们可以在知道错误后立即结束地图应用程序,而无需花费时间在结果已知时将地图应用程序应用于其余元素. 无论如何,我希望一点点迂腐的解释是有帮助的! (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |