PlayFramework: transform json to json - 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)
)

Related

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)
}
}

Json object to map

Hello I would like to transform json object into map using implicit Reads.
With the code below I run into a StackOverflow error, any one could see what the problem is :
"pass": {
"key-1": {
"field1": "aaaa",
"field2": "aaaa"
},
"key-2": {
"field1": "aaaa",
"field2": "aaaa"
},
"key-3": {
"field1": "aaaa",
"field2": "aaaa"
}
}
case class Pass(field1: String, field2: String)
implicit val mapReads: Reads[Map[String, Pass]] = new Reads[Map[String, Pass]] {
def reads(jv: JsValue): JsResult[Map[String, Pass]] =
JsSuccess(jv.as[Map[String, Pass]].map{
case (k, v) => k -> v.asInstanceOf[Pass]
})
}
val passMap = (json \ "pass").validate[Map[String, Pass]]
Here is the stack error :
java.lang.StackOverflowError
at play.api.libs.json.JsReadable$class.as(JsReadable.scala:23)
at play.api.libs.json.JsObject.as(JsValue.scala:124)
at com.MyHelper$$anon$1.reads(MyHelper.scala:51)
at play.api.libs.json.Format$$anon$3.reads(Format.scala:65)
at play.api.libs.json.JsValue$class.validate(JsValue.scala:17)
at play.api.libs.json.JsObject.validate(JsValue.scala:124)
at play.api.libs.json.JsReadable$class.as(JsReadable.scala:23)
at play.api.libs.json.JsObject.as(JsValue.scala:124)
Maybe you will be more likely to create a MapPass class case and use Json.format to do the work for you!
import play.api.libs.json._
val a: String = """{
"pass": {
"key-1": {
"field1": "aaaa",
"field2": "aaaa"
},
"key-2": {
"field1": "aaaa",
"field2": "aaaa"
},
"key-3": {
"field1": "aaaa",
"field2": "aaaa"
}
}
}"""
case class Pass(field1: String, field2: String)
case class MapPass(pass: Map[String, Pass])
implicit val passFormat: Format[Pass] = Json.format[Pass]
implicit val mapPassFormat: Format[MapPass] = Json.format[MapPass]
val json = Json.parse(a)
val mapPassJsResult = json.validate[MapPass]
val mapPass = mapPassJsResult.get
print(mapPass.pass.mkString("\n"))
It worked like that for me:

Json Writes for custom class in scala

I have a Seq of some Strings mostly like below:
val states = Seq(
"CA" -> Seq("Los Angeles" -> Seq("SunsetBlvd", "Hollywood" -> Seq("W 8th St", "W 9th St")), "Pasadena"),
"WA" -> Seq("Seattle", "Redmond")
)
The case class for this could be
case class State(name: String, sub: Option[Seq[State]])
And an implicit Writes
implicit val stateWrites = Json.Writes[State]
Hoping to convert it to a Json like
[
{
"name": "CA",
"sub": [
{
"name": "Los Angeles",
"sub": [
{
"name": "SunsetBlvd"
},
{
"name": "Hollywood",
"sub": [
{
"name": "W 8th St"
},
{
"name": "W 9th St"
}
]
}
]
}
]
},
{
"name": "WA",
"sub": [
{
"name": "Seattle"
},
{
"name": "Redmond"
}
]
}
]
How do I correctly model the data and be able to convert this Seq to a Json using Writes?
Or even change the states val to an appropriate format so I could easily convert it to Json?
In the case class, one of fields is of type itself which is wrong. How do I avoid that in modeling the data or even the Seq?
I came up with something like this:
case class State(name: String, sub: Option[Seq[State]])
import play.api.libs.json._
implicit val optWrites = new Writes[Option[Seq[State]]] {
override def writes(o: Option[Seq[State]]) = {
if (o.isDefined) {
Json.toJson(o.get)(stateSeqWrites)
} else {
JsNull
}
}
}
implicit val stateWrites = new Writes[State] {
def writes(state: State) = {
val l: Seq[(String, JsValueWrapper)] = Seq("name" -> JsString(state.name))
val ll: Seq[(String, JsValueWrapper)] = if (state.sub.isDefined) {
val subValue: JsValueWrapper = Json.toJson(state.sub)(optWrites)
l :+ ("sub" -> subValue)
} else {
l
}
Json.obj(ll : _*)
}
}
implicit val stateSeqWrites: Writes[Seq[State]] = new Writes[Seq[State]] {
override def writes(s: Seq[State]) = {
JsArray(s.map(Json.toJson(_)(stateWrites)))
}
}
val states = Seq(
State("CA", Some(Seq(State("Los Angeles", Some(Seq(State("SunsetBlvd", None), State("Hollywood", Some(Seq(State("W 8th St", None), State("W 9th St", None)))), State("Pasadena", None))))))),
State("WA", Some(Seq(State("Seattle", None), State("Redmond", None))))
)
val json = Json.toJson(states)
println(json.toString())
Probably might get simplified, but it's late night here ;) It does what you need :)
That information has conceptually a tree structure. My advice is to threat it with just a normal case class, to simplify the json formatter and to have a much more semantic structure:
case class Tree(name: String, sub: Option[List[Tree]])
And your formatter would be just like this:
implicit val repositoryFormat: Format[Tree] = (
(__ \ 'name).format[String] ~
(__ \ 'sub).lazyFormatNullable(implicitly[ Format[ List[Tree] ]])
)(Tree.apply, unlift(Tree.unapply))
Notice I used lazyFormatNullable to deal with the recursive reference to Tree in sub.
To simulate the Json you posted, I made the translation to the Tree case class structure.
// The tree leaves
val hollywoodLeaves = Some( Tree("W 8th St", None) :: Tree("W 9th St", None) :: Nil )
val losAngelesLeaves = Some( Tree("SunsetBlvd", None) :: Tree("Hollywood", hollywoodLeaves ) :: Nil )
// The two trees in your array
val firstTree = Tree( "CA", Some( Tree("Los Angeles", losAngelesLeaves) :: Nil ) )
val secondTree = Tree("WA", Some( Tree("Seattle", None) :: Tree("Redmond", None) :: Nil ))
// Your root array
val treeArray = firstTree :: secondTree :: Nil
// Conversion to json
val json = Json.toJson(treeArray)

Use circe to preprocess dot-notation style fields

I have some json that includes some fields that are being flattened into a bson-ish format as in {"foo.bar" : "bash"}. I'd like to transform this to the following representation {"foo" : { "bar" : "bash"}} and wondering where in circe I'd do such an operation. Complicating the problem is that there could be multiple such fields that need to be properly merged, e.g. {"foo.bar" : "a", "foo.bash" : "b", "foo.baz" : "c"} -> {"foo" : { "bar" : "a", "bash" : "b", "baz" : "c"}}.
Here's a quick implementation:
import io.circe.Json
val Dotted = "([^\\.]*)\\.(.*)".r
def expandDotted(j: Json): Json = j.arrayOrObject(
j,
js => Json.fromValues(js.map(expandDotted)),
_.toList.map {
case (Dotted(k, rest), v) => Json.obj(k -> expandDotted(Json.obj(rest -> v)))
case (k, v) => Json.obj(k -> expandDotted(v))
}.reduceOption(_.deepMerge(_)).getOrElse(Json.obj())
)
I haven't really used or tested it in detail, but it seems to work:
scala> import io.circe.literal._
import io.circe.literal._
scala> val j1 = json"""{"foo.bar" : "a", "foo.bash" : "b", "foo.baz" : "c"}"""
j1: io.circe.Json =
{
"foo.bar" : "a",
"foo.bash" : "b",
"foo.baz" : "c"
}
scala> expandDotted(j1)
res1: io.circe.Json =
{
"foo" : {
"baz" : "c",
"bash" : "b",
"bar" : "a"
}
}
And with deeper nesting:
scala> expandDotted(json"""{ "x.y.z": true, "a.b": { "c": 1 } }""")
res2: io.circe.Json =
{
"a" : {
"b" : {
"c" : 1
}
},
"x" : {
"y" : {
"z" : true
}
}
}
And just to confirm that it doesn't mess with undotted keys:
scala> expandDotted(json"""{ "a.b": true, "x": 1 }""").noSpaces
res3: String = {"x":1,"a":{"b":true}}
Note that in the case of "collisions" (paths that lead to both JSON objects and non-object JSON values, or to multiple non-object values), the behavior is that of Json#deepMerge:
scala> expandDotted(json"""{ "a.b": true, "a": 1 }""").noSpaces
res4: String = {"a":1}
scala> expandDotted(json"""{ "a": 1, "a.b": true }""").noSpaces
res5: String = {"a":{"b":true}}
…which is probably what you'd want, but you could also have it fail in these cases, or not expand the colliding path, or do pretty much any other thing you can think of.

json to json transform using play 2.3, combining fields

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)