PlayFramework: how to transform each element of a JSON array - scala

Given the following JSON...
{
"values" : [
"one",
"two",
"three"
]
}
... how do I transform it like this in Scala/Play?
{
"values" : [
{ "elem": "one" },
{ "elem": "two" },
{ "elem": "three" }
]
}

It's easy with Play's JSON Transformers:
val json = Json.parse(
"""{
| "somethingOther": 5,
| "values" : [
| "one",
| "two",
| "three"
| ]
|}
""".stripMargin
)
// transform the array of strings to an array of objects
val valuesTransformer = __.read[JsArray].map {
case JsArray(values) =>
JsArray(values.map { e => Json.obj("elem" -> e) })
}
// update the "values" field in the original json
val jsonTransformer = (__ \ 'values).json.update(valuesTransformer)
// carry out the transformation
val transformedJson = json.transform(jsonTransformer)

You can use Play's JSON APIs:
import play.api.libs.json._
val json = Json parse """
{
"values" : [
"one",
"two",
"three"
]
}
"""
val newArray = json \ "values" match {
case JsArray(values) => values.map { v => JsObject(Seq("elem" -> v)) }
}
// or Json.stringify if you don't need readability
val str = Json.prettyPrint(JsObject(Seq("values" -> JsArray(newArray))))
Output:
{
"values" : [ {
"elem" : "one"
}, {
"elem" : "two"
}, {
"elem" : "three"
} ]
}

Related

Scala play json - update all values with the same key

Let's say I have a JsValue in the form:
{
"businessDetails" : {
"name" : "Business",
"phoneNumber" : "+44 0808 157 0192"
},
"employees" : [
{
"name" : "Employee 1",
"phoneNumber" : "07700 900 982"
},
{
"name" : "Employee 2",
"phoneNumber" : "+44(0)151 999 2458"
}
]
}
I was wondering if there is a way to do an update on every value belonging to a key with a certain name inside a JsValue regardless of its complexity?
Ideally I'd like to map on every phone number to ensure that a (0) is removed if there is one.
I have come across play-json-zipper updateAll but I'm getting unresolved dependency issues when adding the library to my sbt project.
Any help either adding the play-json-zipper library or implementing this in ordinary play-json would be much appreciated.
Thanks!
From what I can see in play-json-zipper project page, you might forgot to add resolver resolvers += "mandubian maven bintray" at "http://dl.bintray.com/mandubian/maven"
If it won't help, and you would like to proceed with custom implementation: play-json does not provide folding or traversing API over JsValue out of the box, so it can be implemented recursively in the next way:
/**
* JSON path from the root. Int - index in array, String - field
*/
type JsPath = Seq[Either[Int,String]]
type JsEntry = (JsPath, JsValue)
type JsTraverse = PartialFunction[JsEntry, JsValue]
implicit class JsPathOps(underlying: JsPath) {
def isEndsWith(field: String): Boolean = underlying.lastOption.contains(Right(field))
def isEndsWith(index: Int): Boolean = underlying.lastOption.contains(Left(index))
def /(field: String): JsPath = underlying :+ Right(field)
def /(index: Int): JsPath = underlying :+ Left(index)
}
implicit class JsValueOps(underlying: JsValue) {
/**
* Traverse underlying json based on given partially defined function `f` only on scalar values, like:
* null, string or number.
*
* #param f function
* #return updated json
*/
def traverse(f: JsTraverse): JsValue = {
def traverseRec(prefix: JsPath, value: JsValue): JsValue = {
val lifted: JsValue => JsValue = value => f.lift(prefix -> value).getOrElse(value)
value match {
case JsNull => lifted(JsNull)
case boolean: JsBoolean => lifted(boolean)
case number: JsNumber => lifted(number)
case string: JsString => lifted(string)
case array: JsArray =>
val updatedArray = array.value.zipWithIndex.map {
case (arrayValue, index) => traverseRec(prefix / index, arrayValue)
}
JsArray(updatedArray)
case `object`: JsObject =>
val updatedFields = `object`.fieldSet.toSeq.map {
case (field, fieldValue) => field -> traverseRec(prefix / field, fieldValue)
}
JsObject(updatedFields)
}
}
traverseRec(Nil, underlying)
}
}
which can be used in the next way:
val json =
s"""
|{
| "businessDetails" : {
| "name" : "Business",
| "phoneNumber" : "+44(0) 0808 157 0192"
| },
| "employees" : [
| {
| "name" : "Employee 1",
| "phoneNumber" : "07700 900 982"
| },
| {
| "name" : "Employee 2",
| "phoneNumber" : "+44(0)151 999 2458"
| }
| ]
|}
|""".stripMargin
val updated = Json.parse(json).traverse {
case (path, JsString(phone)) if path.isEndsWith("phoneNumber") => JsString(phone.replace("(0)", ""))
}
println(Json.prettyPrint(updated))
which will produce desired result:
{
"businessDetails" : {
"name" : "Business",
"phoneNumber" : "+44 0808 157 0192"
},
"employees" : [ {
"name" : "Employee 1",
"phoneNumber" : "07700 900 982"
}, {
"name" : "Employee 2",
"phoneNumber" : "+44151 999 2458"
} ]
}
Hope this helps!

Recursive traverse JSON with circe-optics

I have a json with complex structure. Something like this:
{
"a":"aa",
"b":"bb",
"c":[
"aaa",
"bbb"
],
"d":{
"e":"ee",
"f":"ff"
}
}
And I want to uppercase all string values. The Documentation says:
root.each.string.modify(_.toUpperCase)
But only root values are updated, as expected.
How to make circe-optics traverse all string values recursively?
JSON structure is unknown in advance.
Here is the example on Scastie.
via comments:
I am expecting all string values uppercased, not only root values:
{
"a":"AA",
"b":"BB",
"c":[
"AAA",
"BBB"
],
"d":{
"e":"EE",
"f":"FF"
}
}
Here is a partial solution, as in, it is not fully recursive, but it will solve the issue with the json from your example:
val level1UpperCase = root.each.string.modify(s => s.toUpperCase)
val level2UpperCase = root.each.each.string.modify(s => s.toUpperCase)
val uppered = (level1UpperCase andThen level2UpperCase)(json.right.get)
The following might be a new way to do this. Adding it here for completeness.
import io.circe.Json
import io.circe.parser.parse
import io.circe.optics.JsonOptics._
import monocle.function.Plated
val json = parse(
"""
|{
| "a":"aa",
| "b":"bb",
| "c":[
| "aaa",
| {"k": "asdads"}
| ],
| "d":{
| "e":"ee",
| "f":"ff"
| }
|}
|""".stripMargin).right.get
val transformed = Plated.transform[Json] { j =>
j.asString match {
case Some(s) => Json.fromString(s.toUpperCase)
case None => j
}
}(json)
println(transformed.spaces2)
gives
{
"a" : "AA",
"b" : "BB",
"c" : [
"AAA",
{
"k" : "ASDADS"
}
],
"d" : {
"e" : "EE",
"f" : "FF"
}
}

Adding elements to JSON array using circe and scala

I have a JSON string as the following:
{
"cars": {
"Nissan": [
{"model":"Sentra", "doors":4},
{"model":"Maxima", "doors":4},
{"model":"Skyline", "doors":2}
],
"Ford": [
{"model":"Taurus", "doors":4},
{"model":"Escort", "doors":4}
]
}
}
I would like to add a new cars brand (in addition to Nissan and Ford), using circe at scala.
How could I do it?
Thank you in advance.
You can modify JSON using cursors. One of the possible solutions:
import io.circe._, io.circe.parser._
val cars: String = """
{
"cars": {
"Nissan": [
{"model":"Sentra", "doors":4},
{"model":"Maxima", "doors":4},
{"model":"Skyline", "doors":2}
],
"Ford": [
{"model":"Taurus", "doors":4},
{"model":"Escort", "doors":4}
]
}
}"""
val carsJson = parse(cars).getOrElse(Json.Null)
val teslaJson: Json = parse("""
{
"Tesla": [
{"model":"Model X", "doors":5}
]
}""").getOrElse(Json.Null)
val carsCursor = carsJson.hcursor
val newJson = carsCursor.downField("cars").withFocus(_.deepMerge(teslaJson)).top
Here we just go down to cars field, "focus" on it and pass the function for modifying JSON values. Here deepMerge is used.
newJson will be look as follows:
Some({
"cars" : {
"Tesla" : [
{
"model" : "Model X",
"doors" : 5
}
],
"Nissan" : [
{
"model" : "Sentra",
"doors" : 4
},
{
"model" : "Maxima",
"doors" : 4
},
{
"model" : "Skyline",
"doors" : 2
}
],
"Ford" : [
{
"model" : "Taurus",
"doors" : 4
},
{
"model" : "Escort",
"doors" : 4
}
]
}
})

Casbah (Scala) Generating a query statement with nested conditions ($OR $AND)

Given items: [{ prop1 : "a", prop2 : "b" , prop3, "c",....},....]
and patterns:[{ prop1 : "a", prop2 : "Any"},...]
I want to create a query to find all patterns that match the given items.
The resulting query is of form:
((A or B or C) AND (D or E or F) AND (G or H or J))
or
((A or B or C) AND (D or E or F) AND (G or H or J))
or
((A or B or C) AND (D or E or F) AND (G or H or J))
....
I've tried to build a DSL-form , but I get an ambiguous implicit error on the init:
Can this notation be used? Or how could I implement this with DBObject.Builder or MongoDbObjects?
Thanks,
Eli
import com.mongodb.casbah.query.Imports._
/* test data */
val thing1 = Map[String,String]("thing_type" -> "PC", "os"-> "Windows", "vendor"-> "lenova")
val thing2 = Map[String,String]("thing_type" -> "Tablet", "os"-> "iOS", "vendor"-> "Apple")
"
val things_list = List(thing1, thing2)
/* end test data */
val atts_for_search = List("thing_type", "os", "vendor" )
var pattern_query = $or() // *causes a compilation error
things_list.foreach ( thing => {
var att_and_list = $and() // *causes a compilation error
atts_for_search.foreach ( att => {
att_and_list ++= $or(att $eq thing(att),att $exists false,att $eq "Any")
}) // foreach attribute
pattern_query ++= att_and_list
})
The $or and $and parts of the dsl require a traversable and can't initiated without one - hence the compile error.
If you can just delay the creation of the $and and $or parts of the query until you have built the traversable it works:
import com.mongodb.casbah.query.Imports._
/* test data */
val thing1 = Map[String,String]("thing_type" -> "PC", "os"-> "Windows", "vendor"-> "lenova")
val thing2 = Map[String,String]("thing_type" -> "Tablet", "os"-> "iOS", "vendor"-> "Apple")
val things_list = List(thing1, thing2)
/* end test data */
val atts_for_search = List("thing_type", "os", "vendor" )
val ors = scala.collection.mutable.ListBuffer[DBObject]()
things_list.foreach ( thing => {
var ands = scala.collection.mutable.ListBuffer[DBObject]()
atts_for_search.foreach ( att => {
ands += $or(att $eq thing(att),att $exists false,att $eq "Any")
}) // foreach attribute
ands += $and(ands)
})
val pattern_query = $or(ors)
This returns the following output:
{"$or": [{ "$and": [{ "$or": [ { "thing_type" : "PC"} , { "thing_type" : { "$exists" : false}} , { "thing_type" : "Any"}]} ,
{ "$or": [ { "os" : "Windows"} , { "os" : { "$exists" : false}} , { "os" : "Any"}]} ,
{ "$or": [ { "vendor" : "lenova"} , { "vendor" : { "$exists" : false}} , { "vendor" : "Any"}]}]} ,
{ "$and": [{ "$or": [ { "thing_type" : "Tablet"} , { "thing_type" : { "$exists" : false}} , { "thing_type" : "Any"}]} ,
{ "$or": [ { "os" : "iOS"} , { "os" : { "$exists" : false}} , { "os" : "Any"}]} ,
{ "$or": [ { "vendor" : "Apple"} , { "vendor" : { "$exists" : false}} , { "vendor" : "Any"}]}]}]}

Creating a json in Play/Scala with same keys but different values

Here's what I want to achieve:
{ "user-list" : {
"user" : [
"username" : "foo"
},
{
"username" : "bar"
}
]
}
}
Im using play-framework and scala.
Thanks!
As previous commenters already pointed out, it is not obvious how to help you, given that your json code is invalid (try JSONLint) and that we don't know where it comes from (string? (case) classes from a database? literals?) and what you want to do with it.
Valid json code close to yours would be:
{
"user-list": {
"user": [
{ "username": "foo" },
{ "username": "bar" }
]
}
}
Depending on how much additional information your structure contains, the following might be sufficient (V1):
{
"user-list": [
{ "username": "foo" },
{ "username": "bar" }
]
}
Or even (V2):
{ "user-list": ["foo", "bar"] }
Following the Play documentation, you should be able to generate V1 with:
val jsonObject = Json.toJson(
Map(
"user-list" -> Seq(
toJson(Map("username" -> toJson("foo"))),
toJson(Map("username" -> toJson("bar")))
)
)
)
and V2 with:
val jsonObject = Json.toJson(
Map(
"user-list" -> Seq(toJson("foo"), toJson("bar"))
)
)