json to json transform using play 2.3, combining fields - json

I am trying to combine a couple of fields from a json structure into a another json structure using play 2.3. Basically what I want to do is take this:
{
"a": "aaa"
"b": "bbb"
"c":
{
"d": "ddd"
"e": 123456
"f": "ffff"
}
}
and turn it into this:
{
"a": "aaa"
"b": "bbb"
"new": "ddd123456fff
}
I've had a look at a solution here: https://groups.google.com/forum/#!msg/play-framework/6MdDYf1JjEg/z_WG3DcdQIQJ
but the 'and' isn't available and I don't think 'andThen' is the same thing.

You could do the following
scala> import play.api.libs.json._
scala> val json: JsValue = Json.parse("""
{
"a": "aaa",
"b": "bbb",
"c":
{
"d": "ddd",
"e": 123456,
"f": "ffff"
}
}
""")
scala> case class abc(a:String, b:String, c:String)
Solution:1 Would work with 2.3+
scala> implicit val abcdef: Reads[abc] = new Reads[abc] {
def reads(json:JsValue):JsResult[abc] = {
json match {
case o:JsObject if (o.value.get("a").isDefined && o.value.get("b").isDefined && o.value.get("c").isDefined)=>
o.value.get("a").get match {
case JsString(a)=> o.value.get("b").get match {
case JsString(b)=> o.value.get("c").get match {
case c:JsObject if (c.value.get("d").isDefined && c.value.get("e").isDefined && c.value.get("f").isDefined)=>
c.value.get("d").get match {
case JsString(d)=> c.value.get("e").get match {
case JsNumber(e)=> c.value.get("f").get match {
case JsString(f) => JsSuccess(abc(a,b,d+e+f))
case _ =>JsError("some error")
}
case _ =>JsError("some error")
}
case _ =>JsError("some error")
}
case _ =>JsError("some error")
}
case _ =>JsError("some error")
}
case _ =>JsError("some error")
}
case e => JsError("some error")
}
}
}
scala> json.validate[abc]
Solution:2 Will only work with 2.3.x
scala> implicit val abcdef: Reads[abc] = new Reads[abc] {
def reads(json:JsValue):JsResult[abc] = {
json match {
case JsObject(Seq(("a", JsString(a)), ("b", JsString(b)), ("c", jsobj))) => {
jsobj match {
case JsObject(Seq(("d", JsString(d)), ("e", JsNumber(e)), ("f", JsString(f)))) => JsSuccess(abc(a,b,d+e+f))
case _ => JsError("some error")
}
}
case e => JsError("some error" + e)
}
}
}
scala> json.validate[abc]

most common way to achieve this is to combine Reads as explained here:
https://www.playframework.com/documentation/2.3.x/ScalaJsonCombinators
import play.api.libs.json._, Reads.at
import play.api.libs.functional.syntax._
val js = Json.parse("""{
"a": "aaa",
"b": "bbb",
"c": {
"d": "ddd",
"e": 123456,
"f": "ffff"
}
}""")
val transform = {
val c = (__ \ "c");
{
c.json.prune ~
(__ \ "new").json.copyFrom(
at[String](c \ "d") ~
at[Int] (c \ "e") ~
at[String](c \ "f") apply (_ + _ + _) map (JsString)
)
}.apply(_ ++ _).reads(_)
}
transform(js)

Related

Compare two Json having different arrangement of items using scala

I have two json string having different order of some of the items, so they are like equal, and I want to compare them for equality, but not sure how to achieve it.
JSON 1
{
"data": [
{
"configTemplateId": "44f11ed4-5b08-11ea-8e2d-0242ac132222",
"params": {
"keyOne": "valueOne",
"keyTwo": "valueTwo"
}
},
{
"configTemplateId": "44f11ed4-5b08-11ea-8e2d-0242ac131111",
"params": {
"keyOne": "valueOne",
"keyTwo": "valueTwo"
}
}
]}
JSON 2
{
"data": [
{
"configTemplateId": "44f11ed4-5b08-11ea-8e2d-0242ac131111",
"params": {
"keyOne": "valueOne",
"keyTwo": "valueTwo"
}
},
{
"configTemplateId": "44f11ed4-5b08-11ea-8e2d-0242ac132222",
"params": {
"keyOne": "valueOne",
"keyTwo": "valueTwo"
}
}
]}
I have tried using spray-json as mentioned in Compare json equality in Scala but that does not seem working.
You have to implement some kind of deepEquals. The idea is the following:
import play.api.libs.json.{JsArray, JsObject, JsValue, Json}
def deepEquals(json1: JsValue, json2: JsValue): Boolean = {
def sortArray(json: JsValue): JsValue = json match {
case JsArray(arr) => JsArray(arr.map(sortArray).sortBy(_.toString))
case other => other
}
if (json1.getClass != json2.getClass) {
false
} else {
(json1, json2) match {
case (j1: JsObject, j2: JsObject) =>
val fields1 = j1.value
val fields2 = j2.value
(fields1.keySet ++ fields2.keySet).foldLeft(true) { (acc, key) =>
val field1 = fields1.get(key)
val field2 = fields2.get(key)
acc && ((field1, field2) match {
case (Some(v1), Some(v2)) => deepEquals(v1, v2)
case _ => false
})
}
case (j1: JsArray, j2: JsArray) =>
sortArray(j1) == sortArray(j2)
case (other1, other2) => other1 == other2
}
}
}
I used json play library, but with spray the implementation won't be much different

PlayFramework: transform json to json

i have a json like this
{
"value.first" : "one",
"value.second" : "two",
"value.third" : "three"
}
how do I transform it like this in Scala/Play? :
{
"value": {
"first": "one",
"second": "two",
"third": "three"
}
}
The solution depends on the necessary flexibility and the handling of wrong json format. Maybe the following will work for you.
import play.api.libs.json._
val jsonInitial = Json.obj(
"value.first" -> "one",
"value.second" -> "two",
"value.third" -> "three"
)
val primary: String = jsonInitial.keys.headOption
.map{ _.split('.')(0) }
.getOrElse("empty")
val secondary: Seq[(String, JsValue)] = jsonInitial.fields
.map{ case (k, v) => (k.split('.')(1), v) }
val jsonModified = Json.obj(
primary -> JsObject(secondary)
)

Find the path of an JSON element with dynamic key with Play JSON

I am using Play Framework with Scala. I have the following JSON structure:
{
"a": 1540554574847,
"b": 2,
"c": {
"pep3lpnp1n1ugmex5uevekg5k20wkfq3": {
"a": 1,
"b": 1,
"c": 1,
"d": 1
},
"p3zgudnf7tzqvt50g7lpr2ryno7yugmy": {
"b": [
"d10e5600d11e5517"
],
"c": 1,
"d": 1,
"e": 1,
"g": 1,
"h": [
"d10e5600d11e5517",
"d10e5615d11e5527",
"d10e5605d11e5520",
"d10e5610d11e5523",
"d10e5620d11e5530"
],
"q": "a_z6smu56gstysjpqbzp21ruxii6g2ph00"
},
"33qfthhugr36f5ts4251glpqx0o373pe": {
"b": [
"d10e5633d11e5536"
],
"c": 1,
"d": 1,
"e": 1,
"g": 1,
"h": [
"d10e5638d11e5539",
"d10e5633d11e5536",
"d10e5643d11e5542",
"d10e5653d11e5549",
"d10e5648d11e5546"
],
"q": "a_cydo6wu1ds340j3q6qxeig97thocttsp"
}
}
}
I need to fetch values from paths
"c" -> "pep3lpnp1n1ugmex5uevekg5k20wkfq3" -> "b",
"c" -> "p3zgudnf7tzqvt50g7lpr2ryno7yugmy" -> "b",
"c" -> "33qfthhugr36f5ts4251glpqx0o373pe" -> "b", and so on, where "pep3lpnp1n1ugmex5uevekg5k20wkfq3" is dynamic and changes for every JSON input.
Output should be like Seq(object(q,b,c)).
If you don't need to know which generated key belongs to which value you can use recursive path \\ operator:
import play.api.libs.json.Json
import play.api.libs.json._
val jsonText = """{
"a":1540554574847,
"b":2,
"c":{
"onegeneratedkey":{
"a":1,
"b":1,
"c":1,
"d":1
},
"secondsonegeneratedkey":{
"a":1,
"b": [1, 2, 3],
"c":1,
"d":1
}
}
}"""
val result: Seq[JsValue] = Json.parse(jsonText) \ "c" \\ "b"
// res: List(1, [1,2,3])
UPD.
To get all values stored inside object with generated-keys, one can use JsObject#values:
val valuesSeq: Seq[JsValue] = (Json.parse(jsonText) \ "c").toOption // get 'c' field
.collect {case o: JsObject => o.values.toSeq} // get all object that corresponds to generated keys
.getOrElse(Seq.empty)
// res: Seq({"a":1,"b":1,"c":1,"d":1}, {"a":1,"b":[1,2,3],"c":1,"d":1})
val valuesABC = valuesSeq.map(it => (it \ "a", it \ "b", it \ "c"))
// res: Seq((JsDefined(1),JsDefined(1),JsDefined(1)), (JsDefined(1),JsDefined([1,2,3]),JsDefined(1)))
I misread the question, and this is the modified version.
Here I used json.pick to read JsObject and iterate the keys from there.
Ps: You don't have to create Reads or the case classes, but it should made the caller program more readable.
import play.api.libs.json.Json
import play.api.libs.json._
val jsonText =
"""{
"top": {
"level2a": {
"a": 1,
"b": 1,
"c": 1,
"d": 1
},
"level2b": {
"a": 2,
"b": 2,
"nested": {
"b": "not interested"
}
}
}
}"""
case class Data(k: String, v: Int)
case class Datas(list: Seq[Data])
object Datas {
implicit val reads: Reads[Datas] = (__ \ "top").json.pick.map {
case obj: JsObject =>
new Datas(obj.keys.flatMap(k => (obj \ k \ "b").validate[Int] match {
case JsSuccess(v, _) => Some(Data(k, v))
case _ => None
}).toSeq)
}
}
Json.parse(jsonText).validate[Datas].asOpt match {
case Some(d) => println(s"found: $d")
case _ => println("not found")
}
To deserialize the internal structure within level2, you may choose to create the internal structure and use Json.reads to create the default reads. So long as the data structure is known and predictable.
For example
case class Internal(a: Int, b: Int, c: Option[Int], d: Option[Int])
object Internal {
implicit val reads = Json.reads[Internal]
}
case class Data(k: String, v: Internal)
case class Datas(list: Seq[Data])
object Datas {
implicit val reads: Reads[Datas] = (__ \ "top").json.pick.map {
case obj: JsObject =>
new Datas(obj.keys.flatMap(k => (obj \ k).validate[Internal].asOpt
.map(v => Data(k, v))).toSeq)
}
}

Scala how to read json path by array

My main aim is to make a transformation i.e from "data/message" to "posts/content".
What's wrong with my code? I can't even access the value of "message" in an array.
<code>
import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
def mapping(): JsObject = {
val sjson =
"""
{
"data": [
{"message": "A", "created_time": "a" },
{"message": "B", "created_time": "b" }
],
"page": "test"
}
""".stripMargin
val json = Json.parse(sjson)
val jsonReads =
(__ \ 'data \\ 'message ).read[String]
json.validate(jsonReads).map {
case( message ) =>
Json.obj("content" -> message)
}.get
}
</code>
In response to your comment on this post, here's my updated answer:
val sjson =
"""
{
"data": [
{"message": "A", "created_time": "a" },
{"message": "B", "created_time": "b" }
],
"page": "test"
}
""".stripMargin
// Read the whole object
val json: JsObject = Json.parse(sjson).as[JsObject]
// Read the "data array"
val data: Seq[JsObject] = (json \ "data").as[Seq[JsObject]]
// Get final result my changing "data" to "posts",
// and all instances of "message" to "content" and "created_time" to "created"
val result =
Json.obj(
"posts" -> (data map (d => JsObject(d.value map {
case ("message", m) =>
"content" -> m
case ("created_time", t) =>
"created" -> t
})))
)

How to parse this JSON using Play Framework?

I have the following JSON being returned from a web service:
{
"hits": [
{
"created_at": "2016-02-01T15:01:03.000Z",
"title": "title",
"num_comments": 778,
"parent_id": null,
"_tags": [
"story",
"author",
"story_11012044"
],
"objectID": "11012044",
"_highlightResult": {
"title": {
"value": "title",
"matchLevel": "full",
"matchedWords": [
"title"
]
},
"author": {
"value": "author",
"matchLevel": "none",
"matchedWords": [
]
},
"story_text": {
"value": "Please lead",
"matchLevel": "none",
"matchedWords": [
]
}
}
}
]
}
and I am trying to parse it using the JSON parsing libs in Play Framework. I have the following code:
import play.api.libs.functional.syntax._
import play.api.libs.json._
case class Post(id: Long, date: String, count: Int)
object Post {
implicit val postFormat = Json.format[Post]
implicit val writes: Writes[Post] = (
(JsPath \ "id").write[Long] and
(JsPath \"date").write[String] and
(JsPath \ "count").write[Int]
)(unlift(Post.unapply))
implicit val reads: Reads[Post] = (
(JsPath \ "objectID").read[Long] and
(JsPath \ "created_at").read[String] and
(JsPath \ "num_comments").read[Int]
)(Post.apply _)
}
import play.api.libs.json._
class PostRepo {
val request: WSRequest = ws.url(MY_URL)
def getPosts: Future[Seq[Post]] =
val result: Future[JsValue] = request.get().map(response =>
response.status match {
case 200 => Json.parse(response.body)
case _ => throw new Exception("Web service call failed: " + response.body)
})
result.map( {
jsonvalue => println("JSARRAY: " + jsonvalue);
(jsonvalue \ "hits").as[Seq[Post]]
})
result
}
Now, when I run the code, I am getting the following error:
play.api.http.HttpErrorHandlerExceptions$$anon$1: Execution exception[[JsResultException:
JsResultException(errors:List(((0)/date,List(ValidationError(List(error.path.missing),WrappedArray()))),
((0)/count,List(ValidationError(List(error.path.missing),WrappedArray()))),
((0)/id,List(ValidationError(List(error.path.missing),WrappedArray()))),
((1)/date,List(ValidationError(List(error.path.missing),WrappedArray()))),
((1)/count,List(ValidationError(List(error.path.missing),WrappedArray()))),
((1)/id,List(ValidationError(List(error.path.missing),WrappedArray()))),
((2)/date,List(ValidationError(List(error.path.missing),WrappedArray()))),
((2)/count,List(ValidationError(List(error.path.missing),WrappedArray()))),
((2)/id,List(ValidationError(List(error.path.missing),WrappedArray()))),
((3)/date,List(ValidationError(List(error.path.missing),WrappedArray()))),
((3)/count,List(ValidationError(List(error.path.missing),WrappedArray()))),
((3)/id,List(ValidationError(List(error.path.missing),WrappedArray())))
Obviously something is wrong with the way I'm trying to parse the JSON but I have now spent a few hours trying to figure out the problem and I'm well and truly stuck.
Some refactoring code with Reads.seq
val r = (__ \ "hits").read[Seq[Post]](Reads.seq[Post])
def getPosts: Future[Seq[Post]] = {
WS.url(MY_URL).get().map(response =>
response.status match {
case 200 => r.reads(response.json) match {
case JsError(e) => throw new Exception("Json read fails. Response body:" + response.json.toString() + "\nRead error:" + e.toString())
case JsSuccess(x, _) => x
}
case _ => throw new Exception("Web service call failed: " + response.body)
})
}