如何 "reads" 给定 Json 键名以大写字母开头的对象的 Scala Case Class

How to "reads" into a Scala Case Class given a Json object with key names that start with a capital letter

本题基于 Scala 2.12.12

scalaVersion := "2.12.12"

使用播放-json

"com.typesafe.play" %% "play-json" % "2.9.1"

如果我有一个如下所示的 Json 对象:

{
   "UpperCaseKey": "some value", 
   "AnotherUpperCaseKey": "some other value" 
}

我知道我可以像这样创建案例 class:

case class Yuck(UpperCaseKey: String, AnotherUpperCaseKey: String) 

然后用这个追赶者跟进:

implicit val jsYuck = Json.format[Yuck]

当然,这会给我 reads[Yuck]writes[Yuck] 往返 Json。

我问这个是因为我有一个用例,我不是决定键大小写的人,我收到了一个 Json 对象,该对象充满了以开头的键一个大写字母。

在这个用例中,我将不得不读取和转换数百万个,因此性能是一个问题。

我研究了@JsonAnnotations 和 Scala 的转换器。前者似乎没有太多在现场级别的 Scala 中使用的文档,而后者似乎是很多样板文件,如果我只知道如何使用,另一种方法可能非常简单...

请记住,当您回答这个问题时,某些键将被命名为:

XXXYyyyyZzzzzz

因此预定义的 Snake/Camel 大小写转换将不起作用。

编写自定义转换似乎是一种选择,但不确定如何使用 Scala 来实现。

有没有办法任意请求 Json 读取将获取密钥 "XXXYyyyZzzz" 并将其匹配到 Scala 案例 class 中标记为 "xxxYyyyZzzz" 的字段?

需要说明的是,我可能还需要将名为 "AbCdEf" 的 Json 键转换或至少知道如何转换为标有 "fghi".

的字段

只需使用提供的 PascalCase.

case class Yuck(
  upperCaseKey: String,
  anotherUpperCaseKey: String)

object Yuck {
  import play.api.libs.json._

  implicit val jsonFormat: OFormat[Yuck] = {
    implicit val cfg = JsonConfiguration(naming = JsonNaming.PascalCase)

    Json.format
  }
}

play.api.libs.json.Json.parse("""{
   "UpperCaseKey": "some value", 
   "AnotherUpperCaseKey": "some other value" 
}""").validate[Yuck]
// => JsSuccess(Yuck(some value,some other value),)

play.api.libs.json.Json.toJson(Yuck(
  upperCaseKey = "foo",
  anotherUpperCaseKey = "bar"))
// => JsValue = {"UpperCaseKey":"foo","AnotherUpperCaseKey":"bar"}

我认为 play-json 支持这种场景的唯一方法是定义你自己的 Format

假设我们有:

case class Yuck(xxxYyyyZzzz: String, fghi: String)

所以我们可以在伴生对象上定义Format

object Yuck {
  implicit val format: Format[Yuck] = {
    ((__ \ "XXXYyyyZzzz").format[String] and (__ \ "AbCdEf").format[String]) (Yuck.apply(_, _), yuck => (yuck.xxxYyyyZzzz, yuck.fghi))
  }
}

然后是:

val jsonString = """{ "XXXYyyyZzzz": "first value", "AbCdEf": "second value" }"""
val yuck = Json.parse(jsonString).validate[Yuck]
println(yuck)
yuck.map(yuckResult => Json.toJson(yuckResult)).foreach(println)

将输出:

JsSuccess(Yuck(first value,second value),)
{"XXXYyyyZzzz":"first value","AbCdEf":"second value"}

正如我们所见,XXXYyyyZzzz 被映射到 xxxYyyyZzzzAbCdEf 被映射到 fghi

代码 运行 在 Scastie

您还有另一个选择,就是使用 JsonNaming,正如@cchantep 在评论中所建议的那样。如果你定义:

object Yuck {
  val keysMap = Map("xxxYyyyZzzz" -> "XXXYyyyZzzz", "fghi" -> "AbCdEf")
  implicit val config = JsonConfiguration(JsonNaming(keysMap))
  implicit val fotmat = Json.format[Yuck]
}

运行相同的代码会输出相同的结果。代码运行在 Scastie.