Play framework (Scala) - Getting subset of json that contains arrays - json

I have many very large json-objects that I return from Play Framework with Scala.
In most cases the user doesn't need all the data in the objects, only a few fields. So I want to pass in the paths I need (as query parameters), and return a subset of the json object.
I have looked at using JSON Transformers for this task.
Filter code
def filterByPaths(paths: List[JsPath], inputObject: JsObject) : JsObject = {
paths
.map(_.json.pick)
.map(inputObject.transform)
.filter(_.isSuccess)
.map { case JsSuccess(value, path) => (value, path) }
.foldLeft(Json.obj()) { (obj, jsValueAndPath) =>
val(jsValue, path) = jsValueAndPath
val transformer = __.json.update(path.json.put(jsValue))
obj.transform(transformer).get
}
}
Usage:
val input = Json.obj(
"field1" -> Json.obj(
"field2" -> "right result"
),
"field4" -> Json.obj(
"field5" -> "not included"
),
)
val result = filterByPaths(List(JsPath \ "field1" \ "field2"), input)
// {"field1":{"field2":"right result"}}
Problem
This code works fine for JsObjects. But I can't make it work if there are JsArrays in the strucure. I had hoped that my JsPath could contain an index to look up the field, but that's not the case. (Don't know why I assumed that, maybe my head was too far in the JavaScript-world)
So this would fail to return the first entry in the Array:
val input: JsObject = Json.parse("""
{
"arr1" : [{
"field1" : "value1"
}]
}
""").as[JsObject]
val result = filterByPaths(List(JsPath \ "arr1" \ "0"), input)
// {}
Question
My question is: How can I return a subset of a json structure that contains arrays?
Alternative solution
I have the data as a case class first, and I serialize it to Json, and then run filterByPaths on it. Having a Reader that only creates the json I need in the first place might be a better solution, but creating a Reader on the fly, with configuration from queryparams seamed a more difficult task, then just stripping down the json afterwards.

The example of the returning array element:
val input: JsValue = Json.parse("""
{
"arr1" : [{
"field1" : "value1"
}]
}
""")
val firstElement = (input \ "arr1" \ 0).get
val firstElementAnotherWay = input("arr1")(0)
More about this in the Play Framework documentation: https://www.playframework.com/documentation/2.6.x/ScalaJson
Update
It looks like you got the old issue RuntimeException: expected KeyPathNode. JsPath.json.put, JsPath.json.update can't past an object to a nesting array.
https://github.com/playframework/playframework/issues/943
https://github.com/playframework/play-json/issues/82
What you can do:
Use the JSZipper: https://github.com/mandubian/play-json-zipper
Create a script to update arrays "manually"
If you can afford it, strip array in a resulting object
Example of stripping array (point 3):
def filterByPaths(paths: List[JsPath], inputObject: JsObject) : JsObject = {
paths
.map(_.json.pick)
.map(inputObject.transform)
.filter(_.isSuccess)
.map { case JsSuccess(value, path) => (value, path)}
.foldLeft(Json.obj()) { (obj, jsValueAndPath) =>
val (jsValue, path) = jsValueAndPath
val arrayStrippedPath = JsPath(path.path.filter(n => !(n.toJsonString matches """\[\d+\]""")))
val transformer = __.json.update(arrayStrippedPath.json.put(jsValue))
obj.transform(transformer).get
}
}
val result = filterByPaths(List(JsPath \ "arr1" \ "0"), input)
// {"arr1":{"field1":"value1"}}
The example

The best to handle JSON objects is by using case classes and create implicit Reads and Writes, by that you can handle errors every fields directly. Don't make it complicated.
Don't use .get() much recommended to use .getOrElse() because scala is a type-safe programming language.
Don't just use any Libraries except you know the process behind it, much better to create your own parsing method with simplified solution to save memory.
I hope it will help you..

Related

How can I get a deep element of a json string in scala?

I am using Scala to parse json with a structure like this:
{
"root": {
"metadata": {
"name": "Farmer John",
"hasTractor": false
},
"plants": {
"corn": 137.137,
"soy": 0.45
},
"animals": {
"cow": 4,
"sheep": 12,
"pig": 1
}
}
}
And I am currently using the org.json library to parse it, like this:
val jsonObject = new JSONObject(jsonString) // Where jsonString is the above json tree
But when I run something like jsonObject.get("root.metadata.name") then I get the error:
JSONObject["root.metadata.name"] not found.
org.json.JSONException: JSONObject["root.metadata.name"] not found.
I suspect I can get the objects one at a time by splitting up that path, but that sounds tedious and I assume a better json library already exists. Is there a way to easily get the data the way I am trying to or is there a better library to use that works better with Scala?
The JSONObject you are using is deprecated. The deprecation message sends you to The Scala Library Index. I'll demonstrate how this can be done in play-json. Let's assume that the json above is stored at jsonString, so we can do:
val json = Json.parse(jsonString)
val path = JsPath \ "root" \ "metadata" \ "name"
path(json) match {
case Nil =>
??? // path does nont exist
case values =>
println(s"Values are: $values")
}
Code run at Scastie.
Looks like I was able to solve it using JsonPath like this:
val document = Configuration.defaultConfiguration().jsonProvider().parse(jsonString): Object
val value = JsonPath.read(document, "$.root.metadata.name"): Any

discover type from CSV columns in scala

I want to read a generic CSV file with headers but unknown column number into a typed structure. My question is kind of the same as Strongly typed access to csv in scala? but with the fact I would have no schema to pass to the parser...
Until now, I was using Jackson CSV mapper to read each row as a Map[String,String], and it was working well.
import com.fasterxml.jackson.module.scala.DefaultScalaModule
def genericStringIterator(input: InputStream): Iterator[Map[String, String]] = {
val mapper = new CsvMapper()
mapper.registerModule(DefaultScalaModule)
val schema = CsvSchema.emptySchema.withHeader
val iterator = mapper
.readerFor(classOf[Map[String, String]])
.`with`(schema)
.readValues[Map[String, String]](input)
iterator.asScala
}
Now, we need the field to be typed, so 4.2 would be a Double but "4.2" would still be a String.
We are using play-json everywhere in our project, and so I know JsValue already has a great type inference for generic stuff like that.
As paly-json it is based on Jackson too, I thought it would be great to have something like
import play.api.libs.json.jackson.PlayJsonModule
def genericStringIterator(input: InputStream): Iterator[JsValue] = {
val mapper = new CsvMapper()
mapper.registerModule(PlayJsonModule)
val schema = CsvSchema.emptySchema.withHeader
val iterator = mapper
.readerFor(classOf[JsValue])
.`with`(schema)
.readValues[JsValue](input)
iterator.asScala
}
But when I try the former code, I get an exception :
val iterator = CSV.genericAnyIterator(input(
"""foo,bar,baz
|"toto",42,43
|"tata",,45
| titi,87,88
|"tutu",,
|""".stripMargin))
iterator
.foreach { a =>
println(a)
}
java.lang.RuntimeException: We should be reading map, something got wrong
at play.api.libs.json.jackson.JsValueDeserializer.deserialize(JacksonJson.scala:165)
at play.api.libs.json.jackson.JsValueDeserializer.deserialize(JacksonJson.scala:128)
at play.api.libs.json.jackson.JsValueDeserializer.deserialize(JacksonJson.scala:123)
at com.fasterxml.jackson.databind.MappingIterator.nextValue(MappingIterator.java:277)
at com.fasterxml.jackson.databind.MappingIterator.next(MappingIterator.java:192)
at scala.collection.convert.Wrappers$JIteratorWrapper.next(Wrappers.scala:40)
at scala.collection.Iterator.foreach(Iterator.scala:929)
at scala.collection.Iterator.foreach$(Iterator.scala:929)
at scala.collection.AbstractIterator.foreach(Iterator.scala:1417)
at my.company.csv.CSVSpec$$anon$4.<init>(CSVSpec.scala:240)
Is there something I'm doing wrong ?
I don't care particulary to have a play-json JsValue in the end, any Json structure with generic typed field would be ok. Is there another lib I could use for that ? For what I found, all other libs are based on a mapping given to the CSV Reader in advance, and what is important for me is to be able to infer the type from the CSV.
Ok, I was lazy to want to find something working out of the box :)
In fact it was easy to implement it myself.
I went looking to lib in other languages that does this infering (PapaParse in JS, Pandas in python), and discovered that they were doing a test-and-retry with priority to guess the types.
So I implemented it myself, and it works fine !
Here it is:
def genericAnyIterator(input: InputStream): Iterator[JsValue] = {
// the result of former code, mapping to Map[String,String]
val strings = genericStringIterator(input)
val decimalRegExp: Regex = "(\\d*){1}(\\.\\d*){0,1}".r
val jsValues: Iterator[Map[String, JsValue]] = strings.map { m =>
m.mapValues {
case "" => None
case "false" | "FALSE" => Some(JsFalse)
case "true" | "TRUE" => Some(JsTrue)
case value#decimalRegExp(g1, g2) if !value.isEmpty => Some(JsNumber(value.toDouble))
case "null" | "NULL" => Some(JsNull)
case value#_ => Some(JsString(value))
}
.filter(_._2.isDefined)
.mapValues(_.get)
}
jsValues.map(map => JsObject(map.toSeq))
}
which does in a test
it should "read any csv in JsObject" in new WithInputStream {
val iterator = CSV.genericAnyIterator(input(
"""foo,bar,baz
|"toto",NaN,false
|"tata",,TRUE
|titi,87.79,88
|"tutu",,null
|"tete",5.,.5
|""".stripMargin))
val result: Seq[JsValue] = iterator.toSeq
result should be(Stream(
Json.obj("foo" -> "toto", "bar" -> "NaN", "baz" -> false)
, Json.obj("foo" -> "tata", "baz" -> true)
, Json.obj("foo" -> "titi", "bar" -> 87.79, "baz" -> 88)
, Json.obj("foo" -> "tutu", "baz" -> JsNull)
, Json.obj("foo" -> "tete", "bar" -> 5, "baz" -> 0.5)
) )
}

Validate JSON in Play framework without JSPath

I have pre-defined class MyClass the following JSON structure:
{
"key1": [
// Objects of MyClass
],
"key2": [
// Objects of MyClass
],
"key3": [
// Objects of MyClass
]
}
key1, key2, key3 are optional (at least one must present but I do not need to validate it)
According to the Play framework documentation I need to do the following:
implicit val myClassReads: Reads[] = (
(JsPath \ "key1").read[List[MyClass]] and
(JsPath \ "key2").read[List[MyClass]] and
(JsPath \ "key3").read[List[MyClass]]
)
But this approach has some disadvantages :
The data type of key1, key2, key3 is always MyClass). Repeating read[MyClass] seems redundant.
What if the JSON schema changes in away that there are key1, key2, ... key100 ? The code becomes very messy.
How can I write a custom validator that apply read[MyClass] to all fields?
If you are using Play Json 2.8.x, you can just do:
val result = json.validate[Map[String, List[MyClass]]]
Is it possible to validate whether the JSON keys are in ALLOWED_KEYS: List[String] with your approach?
You can also use another type for the key of the Map, as long as you define a KeyReads instance for that type. So you could use an Enum, with only a limited number of possible values. Or you can use refinement types (e.g. using the "refined" library) and so on.
The easiest way to restrict the possible values of the keys is to check the resulting Map after conversion:
val ALLOWED_KEYS = Set("key1", "key2", ...)
val result = json.validate(
Reads.verifying[Map[String, List[MyClass]]](_.keySet.subsetOf(ALLOWED_KEYS))
)
or you can also check the keys upfront if you want:
val result = json.validate(
Reads.verifying[JsObject](_.keys.subsetOf(ALLOWED_KEYS))
.andThen(Reads.of[Map[String, List[MyClass]]])
)
Something like this ?
def convertJsonToListModel[T](json: JsValue)(implicit reads: Reads[T]): List[T] = {
val conversionResult: JsResult[List[T]] = json.validate[List[T]]
conversionResult match {
case s: JsSuccess[List[T]] => s.get
case e: JsError => e.get
}
}
Play JSON has automatic format derivation for product type's (e.g. case classes).
More details you can find in the documentation: https://www.playframework.com/documentation/latest/ScalaJsonAutomated
I guess in your case this would like something like next:
import play.api.libs.json._
case class MyClass(value: String)
object MyClass {
implicit format = Json.format[Resident]
}
case class MyClassContainer(key1: List[MyClass], key2: List[MyClass], key3: List[MyClass])
object MyClassContainer {
implicit format = Json.format[MyClassContainer]
}
Hope this will be helpful.

Play Scala JSON - conditionally add field to JSON object in Writes

In our app, we have pretty complex structure of objects that is getting converted to JSON and back. Until now most of the formatted are symmetrical (except some very specific cases, and even these for security reasons).
Now we are facing a more complex case where conversion of an object into JSON (writes) needs to create an additional field at a time of conversion while the case class does not have that field.
For example, here is one of our existing formatters:
case class ChecklistColumn(kind: ColumnKind.Value, descriptor: Descriptor.Value, data: JsValue) extends Column
implicit val checklistResultChecklistDataFormat: Format[ChecklistColumn] = (
(__ \ "kind").format[ColumnKind.Value] and
(__ \ "descriptor").format[Descriptor.Value] and
(__ \ "data").format[JsValue]
)(ChecklistColumn.apply, unlift(ChecklistColumn.unapply))
This one creates a json that will looks like:
{
"kind": <String>,
"descriptor": <String>,
"data": <JsValue>
}
What we need to achieve is:
{
"kind": <String>,
"descriptor": <String>,
"data": <JsValue>,
"normalized_data": <JsString>
}
But, only in case when the data is type of JsString (in any other case normalized_data can be left empty, byt ideally should not even exist).
I do understand that we have to create separate Reads & Writes for that.
But, I am not sure, how to implement the logic that will react differently to a different type of data.
Of course, there is always an option to create fully custom writes:
override def writes(column: ChecklistColumn): JsValue = {...}
But, this will create a huge complexity in a code that will be hard to maintain.
What is the cleanest way to implement something like that?
Have a look at ScalaJsonTransformers. You can create a transformer that creates the normalised field from a string data value and use it to, erm, transform your original Format to a new Writes. Here's a slightly simplified example that could doubtless be improved (you'll want to check various edge cases):
case class ChecklistColumn(kind: String, descriptor: String, data: JsValue)
// The original format.
val checklistFormat: Format[ChecklistColumn] = (
(__ \ "kind").format[String] and
(__ \ "descriptor").format[String] and
(__ \ "data").format[JsValue]
)(ChecklistColumn.apply, unlift(ChecklistColumn.unapply))
// A transformer that looks for a "data" field with a string
// value and adds the normalized_data field if it finds one.
val checklistTransformer: Reads[JsObject] = JsPath.json.update(
(__ \ "data").read[String].flatMap (
str => (__ \ "normalized_data").json.put(JsString(str + "!!!"))))
// A new derived Writes which writes the transformed value if
// the transformer succeeds (a data string), otherwise the
// original value.
implicit val checklistWrites: Writes[ChecklistColumn] = checklistFormat
.transform (js => js.transform(checklistTransformer).getOrElse(js))
That gives me:
Json.prettyPrint(Json.toJson(ChecklistColumn("a", "b", JsNumber(1))))
// {
// "kind" : "a",
// "descriptor" : "b",
// "data" : 1
// }
Json.prettyPrint(Json.toJson(ChecklistColumn("a", "b", JsString("c"))))
// {
// "kind" : "a",
// "descriptor" : "b",
// "data" : "c",
// "normalized_data" : "c!!!"
// }

immutable Map (de)serialization to/from Play JSON

I have following (simplified) structure:
case class MyKey(key: String)
case class MyValue(value: String)
Let's assume that I have Play JSON formatters for both case classes.
As an example I have:
val myNewMessage = collection.immutable.Map(MyKey("key1") -> MyValue("value1"), MyKey("key2") -> MyValue("value2"))
As a result of following transformation
play.api.libs.json.Json.toJson(myNewMessage)
I'm expecting something like:
{ "key1": "value1", "key2": "value2" }
I have tried writing the formatter, but somehow I can not succeed:
implicit lazy val mapMyKeyMyValueFormat: Format[collection.immutable.Map[MyKey, MyValue]] = new Format[collection.immutable.Map[MyKey, MyValue]] {
override def writes(obj: collection.immutable.Map[MyKey, MyValue]): JsValue = Json.toJson(obj.map {
case (key, value) ⇒ Json.toJson(key) -> Json.toJson(value)
})
override def reads(json: JsValue): JsResult[collection.immutable.Map[MyKey, MyValue]] = ???
}
I have no idea how to write proper reads function. Is there any simpler way of doing it? I'm also not satisfied with my writes function.
Thx!
The reason the writes method is not working is because you're transforming the Map[MyKey, MyValue] into a Map[JsValue, JsValue], but you can't serialize that to JSON. The JSON keys need to be strings, so you need some way of transforming MyKey to some unique String value. Otherwise you'd be trying to serialize something like this:
{"key": "keyName"} : {"value": "myValue"}
Which is not valid JSON.
If MyKey is as simple as stated in your question, this can work:
def writes(obj: Map[MyKey, MyValue]): JsValue = Json.toJson(obj.map {
case (key, value) => key.key -> Json.toJson(value)
}) // ^ must be a String
Play will then know how to serialize a Map[String, MyValue], given the appropriate Writes[MyValue].
But I'm not certain that's what you want. Because it produces this:
scala> Json.toJson(myNewMessage)
res0: play.api.libs.json.JsValue = {"key1":{"value":"value1"},"key2":{"value":"value2"}}
If this is the output you want:
{ "key1": "value1", "key2": "value2" }
Then your Writes should look more like this:
def writes(obj: Map[MyKey, MyValue]): JsValue = {
obj.foldLeft(JsObject(Nil)) { case (js, (key, value)) =>
js ++ Json.obj(key.key -> value.value)
}
}
Which produces this:
scala> writes(myNewMessage)
res5: play.api.libs.json.JsValue = {"key1":"value1","key2":"value2"}
Reads are easy so long as the structure of MyKey and MyValue are the same, otherwise I have no idea what you'd want it to do. It's very dependent on the actual structure you want. As is, I would suggest leveraging existing Reads[Map[String, String]] and transforming it to the type you want.
def reads(js: JsValue): JsResult[Map[MyKey, MyValue]] = {
js.validate[Map[String, String]].map { case kvMap =>
kvMap.map { case (key, value) => MyKey(key) -> MyValue(value) }
}
}
It's hard to see much else without knowing the actual structure of the data. In general I stay away from having to serialize and deserialize Maps.