Finding element where field equals a number in Play JSON - json

I have a JSON with the (simplified) format "Bar":[{"name":"Foo", "amount":"20.00"}, ...]
What do I need to do in order to find the element with the field amount equals to a number (e.g. 20.00) and return the name field?

I think the best solution is to use case class. I have written this small code, hope this helps.
// define below code in models package
case class Bar(name: String, amount: Double)
implicit val barReads: Reads[Bar] = (
(JsPath \ "name").read[String] and
(JsPath \ "amount").read[Double]
) (Bar.apply _)
// define below code in Application.scala
val jsonString =
"""
[{
"name": "MyName1",
"amount": 20.0
},
{
"name": "MyName2",
"amount": 30.0
}]
"""
val jValue = Json.parse(jsonString)
val Result = jValue.validate[Seq[Bar]].fold(errors => {
println(Json.obj("status" -> "Invalid Request!!", "message" -> JsError.toJson(errors)) + "\n")
},
input => { //input will return Seq[Bar]
for (i <- input) { // looping elements in Seq
if (i.amount == 20) {
println(i.name)
}
}
})
Reference: https://www.playframework.com/documentation/2.5.x/ScalaJson

Related

Scala parses non-canonical JSON

The following data can be seen with different value types. How can I get the desired output?
package ceshi
import scala.util.parsing.json.JSON
object ceshi1212 {
def main(args: Array[String]): Unit = {
class CC[T] {
def unapply(a: Any): Option[T] = Some(a.asInstanceOf[T])
}
object M extends CC[Map[String, Any]]
object L extends CC[List[Any]]
object S extends CC[String]
val jsonString =
"""
{
"languages": [
{
"name": "English",
"is_active": "true",
"completeness": "asdf"
},
{
"name": "Latin",
"is_active": "asdf",
"completeness": "232"
}
,{
"name": "Latin",
"is_active": "0009"
}
,
"error"
]
}
""".stripMargin
// 不规则json error和并列的数组类型不同 怎么解析自动跳过?
val result = for {
Some(M(map)) <- List(JSON.parseFull(jsonString))
L(languages) = map("languages")
M(language) <- languages
S(name) = language("name")
S(active) = language("is_active")
S(completeness) = language.getOrElse("completeness","--")
} yield {
(name, active, completeness)
}
println(result)
//i want result is: List((English,true,asdf), (Latin,asdf,232),(Latain,0009,""))
}
}
i want get result is List((English,true,asdf), (Latin,asdf,232),(Latain,0009,""))
note: 1 The string is not always at the end of the array, and the position is indeterminate
2 The three keys I need may not be complete
As said in the comments there are other libraries to be recommended for working with json have a look at this post to get an overview: What JSON library to use in Scala?
Answer to your question with specific framework (play-json)
Personally I can recommend to use the play json framework.
To obtain the result you have described with play json, your code might look like this:
import play.api.libs.json._
val json: JsValue = Json.parse(jsonString)
val list = (json \ "languages").as[Seq[JsValue]]
val names = list.map(x => ((x\"name").validate[String] match {
case JsSuccess(v, p ) => v
case _ => ""
}
))
val isActives = list.map(x => ((x\"is_active").validate[String] match {
case JsSuccess(v, p ) => v
case _ => ""
}
))
val completeness = list.map(x => ((x\"completeness").validate[String] match {
case JsSuccess(v, p ) => v
case _ => ""
}
))
// need to know in advance what is your max length of your tuple (tmax)
// since 3rd value "completeness" can be missing, so we just take "" instead
val tmax = 3
val res = for(idx <-0 to tmax-1) yield (names(idx),isActives(idx),completeness(idx))
res.toList
// List[(String, String, String)] = List((English,true,asdf), (Latin,asdf,232), (Latin,0009,""))
There's also a very good documentation for the play json framework, just check it out yourself: https://www.playframework.com/documentation/2.8.x/ScalaJson
If you can switch parser library to circe, you can deal with this types of bad data.
Given you have data model
import io.circe.generic.semiauto._
import io.circe.parser.decode
import io.circe.{Decoder, Json}
case class Languages(languages: Seq[Language])
case class Language(name: String, is_active: String, completeness: Option[String])
You can define a fault-tolerant seq decoder that would skip bad data rather than crash whole parse
def tolerantSeqDecoder[A: Decoder]: Decoder[Seq[A]] = Decoder.decodeSeq(Decoder[A]
.either(Decoder[Json])).map(_.flatMap(_.left.toOption))
and the rest...
val jsonString = """
{
"languages": [
{
"name": "English",
"is_active": "true",
"completeness": "asdf"
},
{
"name": "Latin",
"is_active": "asdf",
"completeness": "232"
},
{
"name": "Latin",
"is_active": "0009"
},
"error"
]
}
"""
val languageDecoder = deriveDecoder[Language]
implicit val tolerantDecoder = tolerantSeqDecoder[Language](languageDecoder)
implicit val languagesDecoder = deriveDecoder[Languages]
val parsed = decode[Languages](jsonString)
println(parsed)
out:
Right(Languages(List(Language(English,true,Some(asdf)), Language(Latin,asdf,Some(232)), Language(Latin,0009,None))))
This approach was suggested by one of circe developers: How do I ignore decoding failures in a JSON array?

Dynamic type casting for JsValue field in Scala Play

Since I'm writing a function to request data from another API in my Scala code, the response Json has the format like this:
"data": {
"attributeName": "some String",
"attributeValue": false,
"attributeSource": "Manual",
"attributeValueLabel": null
},
"data": {
"attributeName": "some String",
"attributeValue": "daily",
"attributeSource": "Manual",
"attributeValueLabel": "Almost Daily"
}
Note that sometimes the type of attributeValue is String value, some other time it's a Boolean value.
So I'm trying to write my own Reads and Writes to read the type dynamically.
case class Data(attributeName: Option[String], attributeValue: Option[String], attributeSource: Option[String], attributeValueLabel: Option[String])
object Data{
implicit val readsData: Reads[Data] = {
new Reads[Data] {
def reads(json: JsValue) = {
val attrValue = (json \ "attributeValue").as[] // How to cast to Boolean some time, but some other time is a String here
......
}
}
}
So as you can see in my comment, I'm stuck at the part to cast the (json \ "attributeValue") to String/Boolean, base on the return type of the API. How can I do this?
You can try to parse it as String first and then as Boolean:
val strO = (json \ "attributeValue").asOpt[String]
val value: Option[String] = strO match {
case str#Some(_) => str
case None => (json \ "attributeValue").asOpt[Boolean].map(_.toString)
}
You can use the .orElse function when you are trying to read an attribute in different ways:
import play.api.libs.json.{JsPath, Json, Reads}
import play.api.libs.functional.syntax._
val json1 =
"""
|{
| "attributeName": "some String",
| "attributeValue": false
|}
""".stripMargin
val json2 =
"""
|{
| "attributeName": "some String",
| "attributeValue": "daily"
|}
""".stripMargin
// I modified you case class to make the example short
case class Data(attributeName: String, attributeValue: String)
object Data {
// No need to define a reads function, just assign the value
implicit val readsData: Reads[Data] = (
(JsPath \ "attributeName").read[String] and
// Try to read String, then fallback to Boolean (which maps into String)
(JsPath \ "attributeValue").read[String].orElse((JsPath \ "attributeValue").read[Boolean].map(_.toString))
)(Data.apply _)
}
println(Json.parse(json1).as[Data])
println(Json.parse(json2).as[Data])
Output:
Data(some String,false)
Data(some String,daily)

JSONPath and json4s

I need to extract a field value from the message:
{
"data": {
"code": "404",
...
}
}
JSONPath expression is stored in a variable:
val path = "/data/code"
I'm using json4s for manipulating JSON.
From the doc, one can achieve this using DSL:
val json = parse(message)
val code = json \ "data" \ "code"
It works, but obviously the JSONPath expression should be hardcoded.
Is there any way to evaluate expression stored as string?
Something like:
val code = json.evaluateJSONPath(path)
assuming the json path is provided in a string format delimited by comma the below would work.
import org.json4s._
import org.json4s.native.JsonMethods._
val json = parse(""" { "data": { "code": "404", "foo": "bar" } } """)
val path = "data,code" // json path in string
path.split(',').foldLeft(json)({ case (acc, node) => acc \ node })
Edit:
Adding a implicit class to simplify the access
implicit class JsonHelper(json: JValue) {
def customExtract[T](path: String)(implicit formats: Formats, mf: Manifest[T]) = {
path.split(',').foldLeft(json)({ case (acc: JValue, node: String) => acc \ node }).extract[T]
}
}
json.customExtract[String]("data,code")
res23: String = "404"
How about use the high order function to map the extract command, like:
val extractCodeFunc = (j: JValue) => j \ "data" \ "code"
and extract json:
val res = extractCodeFunc.apply(json)

How to transform a named JsArray into a JsObject with JsZipper

Let's assume either of the following two JSON snippets:
{ "include": ["field1", "field2", "fieldN"] }
{ "exclude": ["field1", "field2", "fieldN"] }
I need to transform the include array like this...
{ "field1": 1, "field2": 1, "fieldN": 1 }
... and the exclude array like this:
{ "field1": 0, "field2": 0, "fieldN": 0 }
[Just for your info: I need to transform input JSON into Mongo's projections.]
Here below is my current solution – I've implemented it as a JsValue extension:
object testTypeExtensions {
implicit class TestJsExtensions(val json: JsValue) extends AnyVal {
def toProjection: JsValue = {
if (json \\ "include" nonEmpty)
JsObject(for (field <- (json \ "include").as[List[JsString]])
yield (field.value, JsNumber(1)))
else if (json \\ "exclude" nonEmpty)
JsObject(for (field <- (json \ "exclude").as[List[JsString]])
yield (field.value, JsNumber(0)))
else Json.obj()
}
}
}
The code above works...
scala> val p = Json.obj("exclude" -> Json.arr("field1", "field2"))
p: play.api.libs.json.JsObject = {"exclude":["field1","field2"]}
scala> p.toProjection
res12: play.api.libs.json.JsObject = {"field1":0,"field2":0}
... but I'm sure it could be written much better with JsZipper.
Furthermore it is not very flexible since it only manages the include and exclude keys, whereas I want to also manage other similar cases like sorting objects:
{ "asc": ["field1", "field2"] }
{ "desc": ["field1", "field2"] }
... transformed into...
{ "field1": 1, "field2": 1 }
... and
{ "field1": -1, "field2": -1 }
That said, what I've in mind is a generic method that manages any kind of named JSON array like:
object testypeExtensions {
implicit class TempJsExtensions(val json: JsValue) extends AnyVal {
def namedArrayToObject(keys: String*): JsValue = {
// how to implement it, possibly with JsZipper
}
}
}
The namedArrayToObject method should search for the specified keys in the current JSON and generate an object for the first match like the ones I described at the beginning of this post, possibly with JsZipper. Here is a simulation of the expected results.
Search for exclude and include and return the first match as a JsObject:
scala> val p = Json.obj("exclude" -> Json.arr("field1", "field2"))
scala> p.namedArrayToObject("exclude", "include")
res12: play.api.libs.json.JsObject = {"field1":0,"field2":0}
Same as before... but now input JSON contains include instead of exclude:
scala> val p = Json.obj("include" -> Json.arr("field1", "field2"))
scala> p.namedArrayToObject("exclude", "include")
res12: play.api.libs.json.JsObject = {"field1":1,"field2":1}
Search for asc and desc and return the first match as a JsObject:
scala> val p = Json.obj("desc" -> Json.arr("field1", "field2"))
scala> p.namedArrayToObject("asc", "desc")
res12: play.api.libs.json.JsObject = {"field1":-1,"field2":-1}
... and so on.
If there is no match, namedArrayToObject should return an empty JsObject. Any suggestion on how to implement this in the right way would be very appreciated.
You can do this pretty straightforwardly with JSON transformations:
import play.api.libs.json._
def toObj(value: Int) = Reads.of[List[String]].map(
keys => Json.toJson(keys.map(_ -> value).toMap)
)
val transformation =
(__ \ 'include).json.update(toObj(1)) andThen
(__ \ 'exclude).json.update(toObj(0))
We can define an example object and apply our transformation:
val example = Json.parse("""{
"include": ["field1", "field2", "field3"],
"exclude": ["field4", "field5", "field6"]
}""")
val transformed = example.transform(transformation)
And then:
scala> transformed.foreach(Json.prettyPrint _ andThen println)
{
"include" : {
"field1" : 1,
"field2" : 1,
"field3" : 1
},
"exclude" : {
"field4" : 0,
"field5" : 0,
"field6" : 0
}
}
This doesn't exactly match your desired API, but it should be easily adaptable, and I'd suggest staying away from the implicit class business, anyway—it's much less composable and makes handling invalid input less elegant.
Travis helped me to find the way... and here below eventually is my solution that does exactly what I was looking for:
object testExtensions {
implicit class testJsExtensions(val json: JsValue) extends AnyVal {
def toParams(pairs: (String, Int)*): JsValue = {
for (pair <- pairs) { json.getOpt(__ \ pair._1).map { values =>
JsObject(for (field <- values.as[List[JsString]])
yield (field.value, JsNumber(pair._2)))
} match {
case Some(params) => return params
case _ =>
}}
Json.obj()
}
}
}
scala> example.toParams(("include", 1), ("exclude", 0))
res63: play.api.libs.json.JsValue = {"field1":1,"field2":1,"field3":1}
scala> example.toParams(("exclude", 0))
res64: play.api.libs.json.JsValue = {"field4":0,"field5":0,"field6":0}
Again a bit thanks to Travis :-)

Play Framework: How to replace all the occurrence of a value in a JSON tree

Given the following JSON...
{ "id":"1234",
"name" -> "joe",
"tokens: [{
"id":"1234",
"id":"2345"
}]
}
... I need to replace the value of all the ids by xxxx like this:
{ "id":"xxxx",
"name" -> "joe",
"tokens: [{
"id":"xxxx",
"id":"xxxx"
}]
}
Let's start create the JSON tree:
val json = Json.obj(
"id" -> "1234",
"name" -> "joe",
"tokens" -> Json.arr(
Json.obj("id" -> "1234"),
Json.obj("id" -> "2345")
)
)
json: play.api.libs.json.JsObject = {"id":"1234","name":"joe","tokens":[{"id":"1234"},{"id":"2345"}]}
Then, getting all the ids is very simple:
json \\ "id"
res64: Seq[play.api.libs.json.JsValue] = List("1234", "1234", "2345")
Now, how do I replace the value of all the ids by xxxx?
There doesn't appear to be a nice way to do this with the standard Play JSON library, although I'd be happy to be proved wrong in that regard. You can however do it easily using the play-json-zipper extensions:
import play.api.libs.json._
import play.api.libs.json.extensions._
val json = Json.obj(
"id" -> "1234",
"name" -> "joe",
"tokens" -> Json.arr(
Json.obj("id" -> "1234"),
Json.obj("id" -> "2345")
)
)
// Using `updateAll` we pattern match on a path (ignoring
// the existing value, as long as it's a string) and replace it
val transformed = json.updateAll {
case (__ \ "id", JsString(_)) => JsString("xxxx")
}
// play.api.libs.json.JsValue = {"id":"xxxx","name":"joe","tokens":[{"id":"xxxx"},{"id":"xxxx"}]}
To make that a re-usable function:
def replaceValue(json: JsValue, key: String, replacement: String) = json.updateAll {
case (__ \ path, JsString(_)) if path == key => JsString(replacement)
}
The json-zipper extensions are still "experimental", but if you want to add them to your project add the following to your project/Build.scala appDependencies:
"play-json-zipper" %% "play-json-zipper" % "1.0"
and the following resolver:
"Mandubian repository releases" at "https://github.com/mandubian/mandubian-mvn/raw/master/releases/"
Probably it isn't most efficient way to do it, but you can try to convert your JSON to an object copy it with new fields and then convert it back to json. Unfortunately currently I don't have environment to check the code, but it should be something like this:
case class MyId(id: String)
case class MyObject(id: String, name: String, tokens: List[MyId])
implicit val idFormat = Json.format[MyId]
implicit val objectFormat = Json.format[MyObject]
val json = Json.parse(jsonString)
val jsResult = Json.fromJson[MyObject](json)
val obj = jsResult match {
case JsSuccess(s, _) => s
case _ => throw new IllegalStateException("Unexpected")
}
val newObj = obj.copy(id = "xxxx")
val result = Json.toJson(newObj)