I am new to Scala and please help me out.
I have 2 Json file. I want to sort first json with a key from the second json.
For eg:
First Json
{
"id": 1,
"response" : [{
"user_id" : 1,
"products" : [
{
"product_id": 10,
"price": 200
},
{
"product_id": 13,
"price": 210
},
{
"product_id": 9,
"price": 320
}
]
},{
"user_id" : 2,
"products" : [
{
"product_id": 15,
"price": 200
},
{
"product_id": 13,
"price": 210
},
{
"product_id": 8,
"price": 320
}
]
}
]
}
And My Second Json
{
"sort": [
{
"user_id": 1,
"products": [
{
"id": 8,
"rank": 5
},
{
"id": 9,
"rank": 1
},
{
"id": 10,
"rank": 3
},
{
"id": 13,
"rank": 2
},{
"id": 15,
"rank": 6
},{
"id": 17,
"rank": 4
},{
"id": 20,
"rank": 7
},{
"id": 21,
"rank": 8
},{
"id": 23,
"rank": 9
}
]
},{
"user_id": 2,
"products": [
{
"id": 8,
"rank": 5
},
{
"id": 9,
"rank": 1
},
{
"id": 10,
"rank": 3
},
{
"id": 13,
"rank": 2
},{
"id": 15,
"rank": 6
},{
"id": 17,
"rank": 4
},{
"id": 20,
"rank": 7
},{
"id": 21,
"rank": 8
},{
"id": 23,
"rank": 9
}
]
}
]
}
I want to sort my first json with respect to the rank I have in second Json.
Output should be like each user should have his products in sorted order based on the rank that is specified for each user on the second JSON.
This is so far what I have tried
def sortedRes() = Action.async {
val url = //1st json url
val sortUrl = //2nd json url
ws.url(url).get().map { response =>
val value: JsValue = Json.parse(response.body)
val result: Either[Exception, SampleResponses] = value.validate[SampleResponses] match {
case JsSuccess(searchResponse, _) =>
Right(searchResponse)
case JsError(errors) =>
Left(new Exception("Couldn't parse Search API response"))
}
val values: List[SampleResponse] = result.right.get.responses
ws.url(sortUrl).get().map { response =>
val sorted: JsValue = Json.parse(response.body)
val sortRespResult: Either[Exception, Sort] = sorted.validate[Sort] match {
case JsSuccess(sortResponse, _) =>
Right(sortResponse)
case JsError(errors) =>
Left(new Exception("Couldn't parse because of these errors : " + errors))
}
val prodRankDetails: List[SampleRank] = sortRespResult.right.get.sort
println("prod = " + prodRankDetails.head.products.sortWith(_.rank > _.rank))
}
Ok(Json.toJson(result.right.get))
}
}
In the last print statement I got the second json's first users product sorted. What I am trying to get is my first json sorted based on the second user.
Here is my model class
package models
import play.api.libs.functional.syntax._
import play.api.libs.json.Reads._
import play.api.libs.json._ // Combinator syntax
object sample {
case class SampleProduct(productId:Int, price: Int)
case class SampleResponse(userId: Int, products: List[SampleProduct])
case class SampleResponses(id: Int, responses: List[SampleResponse])
case class SampleRankedProduct(id: Int, rank: Int)
case class SampleRank(userId: Int, products: List[SampleRankedProduct])
case class Sort(sort: List[SampleRank])
implicit val productReads: Reads[SampleProduct] = (
(JsPath \ "product_id").read[Int] and
(JsPath \ "price").read[Int]
)(SampleProduct.apply _)
implicit val productWrites: Writes[SampleProduct] = (
(JsPath \ "product_id").write[Int] and
(JsPath \ "price ").write[Int]
)(unlift(SampleProduct.unapply))
implicit val responseReads: Reads[SampleResponse] = (
(JsPath \ "user_id").read[Int] and
(JsPath \ "products").read[List[SampleProduct]]
)(SampleResponse.apply _)
implicit val responseWrites: Writes[SampleResponse] = (
(JsPath \ "user_id").write[Int] and
(JsPath \ "products").write[List[SampleProduct]]
)(unlift(SampleResponse.unapply))
implicit val responsesReads: Reads[SampleResponses] = (
(JsPath \ "id").read[Int] and
(JsPath \ "response").read[List[SampleResponse]]
)(SampleResponses.apply _)
implicit val responsesWrites: Writes[SampleResponses] = (
(JsPath \ "id").write[Int] and
(JsPath \ "response").write[List[SampleResponse]]
)(unlift(SampleResponses.unapply))
implicit val rankedProductReads: Reads[SampleRankedProduct] = (
(JsPath \ "id").read[Int] and
(JsPath \ "rank").read[Int]
)(SampleRankedProduct.apply _)
implicit val rankedProductWrites: Writes[SampleRankedProduct] = (
(JsPath \ "id").write[Int] and
(JsPath \ "rank ").write[Int]
)(unlift(SampleRankedProduct.unapply))
implicit val rankReads: Reads[SampleRank] = (
(JsPath \ "user_id").read[Int] and
(JsPath \ "products").read[List[SampleRankedProduct]]
)(SampleRank.apply _)
implicit val rankWrites: Writes[SampleRank] = (
(JsPath \ "user_id").write[Int] and
(JsPath \ "products").write[List[SampleRankedProduct]]
)(unlift(SampleRank.unapply))
implicit val sortReads: Reads[Sort] = (JsPath \ "sort").read[List[SampleRank]].map(x ⇒ Sort(x))
implicit val sortWrites: Writes[Sort] = (__ \ "sort").write[List[SampleRank]].contramap { (person: Sort) => person.sort }
}
An approach for sorting the first JSON using the ranking from the second one, is something like that:
....
val prodRankDetails: List[SampleRank] = sortRespResult.right.get.sort
val sortedResults = values.copy(
responses = values.responses.map { resultForUser =>
val rankingForUser = prodRankDetails
.find(_.userId == resultForUser.userId)
.getOrElse(throw new RuntimeException(s"Rank not found for user ${resultForUser.userId}"))
.products
.map(product => (product.id, product.rank))
.toMap
resultForUser.copy(
products = resultForUser.products.sortWith((a, b) =>
rankingForUser.getOrElse(a.productId, Int.MaxValue) < rankingForUser.getOrElse(b.productId, Int.MaxValue))
)
}
)
Ok(Json.toJson(sortedResults))
that should be performed just after you got prodRankDetails. And also the response will be available inside the .map as you are managing a Future.
So you have to move also the Ok(...) inside that map and to flatMap the outer Future.
Hope this helps.
Related
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)
}
}
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)
})
}
I have the following case classes and JSON combinators:
case class Commit(
sha: String,
username: String,
message: String
)
object Commit {
implicit val format = Json.format[Commit]
}
case class Build(
projectName: String,
parentNumber: String,
commits: List[Commit]
)
val buildReads: Reads[Build] =
for {
projectName <- (__ \ "buildType" \ "projectName").read[String]
name <- (__ \ "buildType" \ "name").read[String]
parentNumber <- ((__ \ "artifact-dependencies" \ "build")(0) \ "number").read[String]
changes <- (__ \ "changes" \ "change").read[List[Map[String, String]]]
} yield {
val commits = for {
change <- changes
sha <- change.get("version")
username <- change.get("username")
comment <- change.get("comment")
} yield Commit(sha, username, comment)
Build(s"$projectName::$name", parentNumber, commits)
}
My JSON reads combinator for Build will handle incoming JSON such as:
{
"buildType": {
"projectName": "foo",
"name": "bar"
},
"artifact-dependencies": {
"build": [{
"number": "1"
}]
},
"changes": {
"change": [{
"verison": "1",
"username": "bob",
"comment": "foo"
}]
}
}
However, if artifact-dependencies is missing, it will fall over. I would like this to be optional.
Should I use readNullable? I have tried to do so, but this fails because it is a nested property.
Does this look pragmatic, or am I abusing JSON combinators to parse my JSON into a case class?
Currently the Format[Commit] in its companion object isn't being used. There's no reason we can't use simple combinators for that, and separate the logic.
case class Commit(sha: String, username: String, message: String)
object Commit {
implicit val reads: Reads[Commit] = (
(__ \ "version").read[String] and
(__ \ "username").read[String] and
(__ \ "comment").read[String]
)(Commit.apply _)
}
Then, if "artifact-dependencies" can be missing, we should make parentNumber an Option[String] in Build.
case class Build(projectName: String, parentNumber: Option[String], commits: List[Commit])
I split the Reads that combines project names into a separate one to make the Reads[Build] look a little more clean.
val nameReads: Reads[String] = for {
projectName <- (__ \ "projectName").read[String]
name <- (__ \ "name").read[String]
} yield s"$projectName::$name"
Then, for when "artifact-dependencies" is missing, we can use orElse and Reads.pure(None) to fill it with None when that entire branch (or sub-branch) is not there. In this case, that would be simpler than mapping each step of the way.
implicit val buildReads: Reads[Build] = (
(__ \ "buildType").read[String](nameReads) and
((__ \ "artifact-dependencies" \ "build")(0) \ "number").readNullable[String].orElse(Reads.pure(None)) and
(__ \ "changes" \ "change").read[List[Commit]]
)(Build.apply _)
val js2 = Json.parse("""
{
"buildType": {
"projectName": "foo",
"name": "bar"
},
"changes": {
"change": [{
"version": "1",
"username": "bob",
"comment": "foo"
}]
}
}
""")
scala> js2.validate[Build]
res6: play.api.libs.json.JsResult[Build] = JsSuccess(Build(foo::bar,None,List(Commit(1,bob,foo))),)
I try to have my formats match the json as closely as possible. Admittedly, in this case it's a bit awkward, but that's because the json schema is kind of weird. Here's how I would do it given those limitations:
import play.api.libs.functional.syntax._
import play.api.libs.json._
case class Build(buildType: BuildType, `artifact-dependencies`: Option[ArtifactDependencies], changes: Changes)
case class BuildType(projectName: String, name: String)
case class ArtifactDependencies(build: List[DependencyInfo])
case class DependencyInfo(number: String)
case class Changes(change: List[Commit])
case class Commit(version: String, username: String, comment: String)
object BuildType {
implicit val buildTypeReads: Reads[BuildType] = (
(JsPath \ "projectName").read[String] and
(JsPath \ "name").read[String]
)(BuildType.apply _)
}
object ArtifactDependencies {
implicit val artifactDependencyReads: Reads[ArtifactDependencies] =
(JsPath \ "build").read[List[DependencyInfo]].map(ArtifactDependencies.apply)
}
object DependencyInfo {
implicit val dependencyInfoReads: Reads[DependencyInfo] =
(JsPath \ "number").read[String].map(DependencyInfo.apply)
}
object Changes {
implicit val changesReads: Reads[Changes] =
(JsPath \ "change").read[List[Commit]].map(Changes.apply)
}
object Commit {
implicit val commitReads: Reads[Commit] = (
(JsPath \ "version").read[String] and
(JsPath \ "username").read[String] and
(JsPath \ "comment").read[String]
)(Commit.apply _)
}
object Build {
implicit val buildReads: Reads[Build] = (
(JsPath \ "buildType").read[BuildType] and
(JsPath \ "artifact-dependencies").readNullable[ArtifactDependencies] and
(JsPath \ "changes").read[Changes]
)(Build.apply _)
def test() = {
val js = Json.parse(
"""
|{
| "buildType": {
| "projectName": "foo",
| "name": "bar"
| },
| "changes": {
| "change": [{
| "version": "1",
| "username": "bob",
| "comment": "foo"
| }]
| }
|}
""".stripMargin)
println(js.validate[Build])
val js1 = Json.parse(
"""
|{
| "buildType": {
| "projectName": "foo",
| "name": "bar"
| },
| "artifact-dependencies": {
| "build": [{
| "number": "1"
| }]
| },
| "changes": {
| "change": [{
| "version": "1",
| "username": "bob",
| "comment": "foo"
| }]
| }
|}
""".stripMargin)
println(js1.validate[Build])
}
}
The output is:
[info] JsSuccess(Build(BuildType(foo,bar),None,Changes(List(Commit(1,bob,foo)))),)
[info] JsSuccess(Build(BuildType(foo,bar),Some(ArtifactDependencies(List(DependencyInfo(1)))),Changes(List(Commit(1,bob,foo)))),)
Note that the slightly awkward
(JsPath \ "change").read[List[Commit]].map(Changes.apply)
is necessary for single argument case classes.
EDIT:
The crucial part I missed is that parentNumber now becomes a method defined on Build as follows:
case class Build(buildType: BuildType, `artifact-dependencies`: Option[ArtifactDependencies], changes: Changes) {
def parentNumber: Option[String] = `artifact-dependencies`.flatMap(_.build.headOption.map(_.number))
}
I am trying to parse and transform a json file so that it can fit a DB structure. I have the following json file.
{
"request": {
"Target": "AR",
"Format": "jsonp",
"Service": "HasOff",
"Version": "2",
"NetworkId": "xyz",
"Method": "getConversions",
"api_key": "xyz",
"fields": [
"Offer.name",
"Stat.datetime",
"Stat.conversion_status",
"Stat.approved_payout",
"Stat.ip",
"Stat.ad_id",
"Stat.affiliate_info1",
"Stat.offer_id"
],
"data_start": "2015-04-16 23:59:59",
"data_end": "2015-04-18 00:00:00",
"callback": "angular.callbacks._3",
"_ga": "GA1.2.1142892596.1429262595",
"_gat": "1"
},
"response": {
"status": 1,
"httpStatus": 200,
"data": {
"page": 1,
"current": 100,
"count": 521,
"pageCount": 6,
"data": [
{
"Offer": {
"name": "ABC Company"
},
"Stat": {
"datetime": "2015-04-19 12:09:01",
"conversion_status": "approved",
"approved_payout": "26.94000",
"ip": "111.11.11.1",
"ad_id": "123456",
"affiliate_info1": "123456",
"offer_id": "260"
}
},
{
"Offer": {
"name": "ABC Company"
},
"Stat": {
"datetime": "2015-04-11 01:01:38",
"conversion_status": "approved",
"approved_payout": "44.94000",
"ip": "49.204.222.117",
"ad_id": "123456",
"affiliate_info1": "123456",
"offer_id": "260"
}
},
To process it I wrote a case class, model the class and try to convert the json into the model as given in -- https://www.playframework.com/documentation/2.2.x/ScalaJson. The code snippet is given below. I have reduced the number of parameters in the case class for sake of brevity.
case class ConversionReport(offerName: String, date: DateTime)
implicit val readConversion: Reads[ConversionReport] = (
(__ \ "response" \ "data" \ "data" \ "Offer" \ "name").read[String] and
(__ \ "response" \ "data" \ "data" \ "Stat" \ "datetime").read[DateTime]
) (ConversionReport)
def getConversionReport(){
val conversionUrl: String = "http://some_link"
val holder = WS.url(conversionUrl).get()
val result = Await.result(holder, Duration(10, "second")).json
val conversionResult: JsResult[ConversionReport] = result.validate[ConversionReport]
println ("xxxx The conversion result is " +conversionResult)
}
When I execute the code, I am getting the error --
xxxx The conversion result is JsError(List((/offerName,List(ValidationError(error.path.missing,WrappedArray()))), (/date,List(ValidationError(error.path.missing,WrappedArray())))))
I guess, I am not able to properly model the json file.
Any help is appreciated.
In this case data is an array, so your approach will not work. You will have to change few things.
import play.api.libs.json._
import play.api.libs.json.Reads._
case class ConversionReport(offerName: String, date: DateTime)
implicit val readConversion: Reads[ConversionReport] = (
(__\ "Offer" \ "name").read[String] and
(__ \ "Stat" \ "datetime").read[DateTime]
) (ConversionReport)
case class FullReport( reportList: Seq[ ConversionReport ] )
implicit val readFullReport: Reads[ FullReport ] = (
(__ \ "response" \ "data" \ "data").lazyRead( Reads.seq[ ConversionReport ]( readConversion ) )
) ( FullReport )
def getConversionReport(){
val conversionUrl: String = "http://some_link"
val holder = WS.url(conversionUrl).get()
val result = Await.result(holder, Duration(10, "second")).json
val conversionResult: JsResult[FullReport] = result.validate[FullReport]
println ("xxxx The conversion result is " +conversionResult)
}
I have a Json of the following format:
{
"user": {
"id": "1",
"name": "Some User",
"permGroups": [
{
"id": "group1",
"name": "Group 1",
"actions": [
{
"id": "action1",
"name": "Action 1"
}
]
},
{
"id": "group2",
"name": "Group 2",
"actions": [
{
"id": "action2",
"name": "Action 2"
},
{
"id": "action3",
"name": "Action 3"
}
]
}
]
},
"title": "New Role",
"role_id": "56fea66c"
}
How can I make a JSON Transformer in Play! 2.1, that will turn that into:
{
"name": "New Role",
"id" : "56fea66c",
"permGroupIds": ["group1","group2"]
"actions": ["action1", "action2", "action3"]
}
I have this working so far:
import play.api.libs.json._
import play.api.libs.functional.syntax._
import play.api.libs.json.Reads._
val jsonStr = """{"user":{"id":"1","name":"Some User","permGroups":[{"id":"group1","name":"Group 1","actions":[{"id":"action1","name":"Action 1"}]},{"id":"group2","name":"Group 2","actions":[{"id":"action3","name":"Action 3"},{"id":"action3","name":"Action 3"}]}]},"title":"New Role","role_id":"56fea66c"}"""
val jsonVal = Json.parse(jsonStr)
val jsonTransformer = (
(__ \ 'name).json.copyFrom((__ \ 'title).json.pick) and
(__ \ 'id).json.copyFrom((__ \ 'role_id).json.pick)
).reduce
jsonVal.transform(jsonTransformer)
this produces:
{
"name": "New Role",
"id" : "56fea66c",
}
Here is one way to do it. This will ignore any perm groups or actions that don't have an id.
import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
val jsonStr = """{"user":{"id":"1","name":"Some User","permGroups":[{"id":"group1","name":"Group 1","actions":[{"id":"action1","name":"Action 1"}]},{"id":"group2","name":"Group 2","actions":[{"id":"action3","name":"Action 3"},{"id":"action3","name":"Action 3"}]}]},"title":"New Role","role_id":"56fea66c"}"""
val jsonTransformer = (
(__ \ 'name).json.copyFrom((__ \ 'user \ 'name).json.pick) and
(__ \ 'id).json.copyFrom((__ \ 'role_id).json.pick) and
(__ \ 'permGroupIds).json.copyFrom((__ \ 'user \ 'permGroups).read[List[JsObject]].map(permGroups =>
JsArray(permGroups.flatMap(permGroup => (permGroup \ "id").toOption))
)) and
(__ \ 'actions).json.copyFrom((__ \ 'user \ 'permGroups).read[List[JsObject]].map(permGroups =>
JsArray(permGroups.flatMap(permGroup => (permGroup \ "actions").asOpt[JsArray]).flatMap(_ \\ "id"))
))
).reduce
val transformed = Json.parse(jsonStr).transform(jsonTransformer)
Json.prettyPrint(transformed.get)
This produces
{
"name" : "Some User",
"id" : "56fea66c",
"permGroupIds" : [ "group1", "group2" ],
"actions" : [ "action1", "action3", "action3" ]
}