This question is based upon Scala 2.12.12
scalaVersion := "2.12.12"
using play-json
"com.typesafe.play" %% "play-json" % "2.9.1"
If I have a Json object that looks like this:
{
"UpperCaseKey": "some value",
"AnotherUpperCaseKey": "some other value"
}
I know I can create a case class like so:
case class Yuck(UpperCaseKey: String, AnotherUpperCaseKey: String)
and follow that up with this chaser:
implicit val jsYuck = Json.format[Yuck]
and that will, of course, give me both reads[Yuck]
and writes[Yuck]
to and from Json.
I'm asking this because I have a use case where I'm not the one deciding the case of the keys and I've being handed a Json object that is full of keys that start with an uppercase letter.
In this use case I will have to read and convert millions of them so performance is a concern.
I've looked into @JsonAnnotations and Scala's transformers. The former doesn't seem to have much documentation for use in Scala at the field level and the latter seems to be a lot of boilerplate for something that might be very simple another way if I only knew how...
Bear in mind as you answer this that some Keys will be named like this:
XXXYyyyyZzzzzz
So the predefined Snake/Camel case conversions will not work.
Writing a custom conversion seems to be an option yet unsure how to do that with Scala.
Is there a way to arbitrarily request that the Json read will take Key "XXXYyyyZzzz"
and match it to a field labeled "xxxYyyyZzzz"
in a Scala case class?
Just to be clear I may also need to convert, or at least know how, a Json key named "AbCdEf"
into field labeled "fghi"
.
I think that the only way play-json support such a scenario, is defining your own Format
.
Let's assume we have:
case class Yuck(xxxYyyyZzzz: String, fghi: String)
So we can define Format
on the companion object:
object Yuck {
implicit val format: Format[Yuck] = {
((__ \ "XXXYyyyZzzz").format[String] and (__ \ "AbCdEf").format[String]) (Yuck.apply(_, _), yuck => (yuck.xxxYyyyZzzz, yuck.fghi))
}
}
Then the following:
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)
Will output:
JsSuccess(Yuck(first value,second value),)
{"XXXYyyyZzzz":"first value","AbCdEf":"second value"}
As we can see, XXXYyyyZzzz
was mapped into xxxYyyyZzzz
and AbCdEf
into fghi
.
Code run at Scastie.
Another option you have, is to usd JsonNaming
, as @cchantep suggested in the comment. If you define:
object Yuck {
val keysMap = Map("xxxYyyyZzzz" -> "XXXYyyyZzzz", "fghi" -> "AbCdEf")
implicit val config = JsonConfiguration(JsonNaming(keysMap))
implicit val fotmat = Json.format[Yuck]
}
Running the same code will output the same. Code ru nat Scastie.