How to deserialize complex JSON with Scala SprayJSON? - json

I have the following JSON:
{
"key1":[
{
"key2":{
"key3":"value3",
"key4":"value4"
},
"key5":{
"key6":"value6"
},
"key7":[
{
"key8":"value8",
"key9":"value9"
}
],
"key10":"value10",
"key11":"value11"
}
],
"key12":"value12"
}
How can I retrieve nested elements (e.g. value6) using SprayJson.
I managed to only retrieve the top level key "key1".
case class Key1(key1: JsArray)
object Key1Protocol extends DefaultJsonProtocol {
implicit val key1: RootJsonFormat[Key1] = jsonFormat1(Key1)
}
<jsonString>.parseJson.convertTo[Key1]

This can help:
case class Key1(key1: JsArray)
object Key1Protocol extends DefaultJsonProtocol {
implicit val key1: RootJsonFormat[Key1] = jsonFormat1(Key1)
}
import spray.json._
import DefaultJsonProtocol._
object MainJson {
import Key1Protocol._
def main(args: Array[String]): Unit = {
val jsonAst = TestComplexJson.str.parseJson.convertTo[Key1]
val result = jsonAst.key1
.elements(0)
.asJsObject
.getFields("key5")(0)
.asJsObject()
.getFields("key6")(0)
println(result)
}
}
gives "value6"

Related

Parsing a recursive trait playsafe json scala

I am using play-json_2.11, and I am trying to parse recursively some case classes
sealed trait Tree
case class Node(value: Float, child: Seq[Tree]) extends Tree
case class Leaf(leaf: Float) extends Tree
So basically, each Node contains a value and a list of Trees (which can be a Node or a Leaf).
So I am defining implicit readers in the companion objects of the case classes. and one in the object called Tree
object Node {
implicit val reader = Json.reads[Node]
}
object Leaf {
implicit val reader = Json.reads[Leaf]
}
object Tree {
implicit val treeReads =
__.read[Node].map(x => x:Tree) orElse __.read[Leaf].map(x => x:Tree)
}
As the parsers are referencing to each other, I cannot define them and get the following error:
ScalaFiddle.scala:9: error: No instance of play.api.libs.json.Reads is available for scala.Seq[ScalaFiddle.Tree] in the implicit scope (Hint: if declared in the same file, make sure it's declared before)
implicit val reader = Json.reads[Node]
How can I parse a Tree in this case? (I do not need it to be specifically a Trait)
Here is the fiddle I tried https://scalafiddle.io/sf/sX8OkWI/3
My input is a json like this one
{
"value": 1.0,
"child": {
"leaf": 2.0
}
}
And I would like to parse it to have
Node(1.0, Leaf(2.0))
This is what you need
import play.api.libs.json._
import play.api.libs.functional.syntax._
sealed trait Tree
case class Node(value: Float, child: Tree) extends Tree
object Node {
implicit lazy val reader = Json.reads[Node]
}
case class Leaf(leaf: Float) extends Tree
object Leaf {
implicit lazy val reader = Json.reads[Leaf]
}
object Tree {
implicit lazy val treeReads: Reads[Tree] =
__.lazyRead(Node.reader).map(x => x:Tree) orElse __.lazyRead(Leaf.reader).map(x => x:Tree)
}
val json: JsValue = Json.parse("""
{
"value": 5.0,
"child": {
"leaf": 7
}
}
""")
println(json)
json.validate[Tree] match {
case s: JsSuccess[Tree] => {
val place: Tree = s.get
println(place)
}
case e: JsError => {
println(e)
}
}
You don't need the implicits in the companion objects.. or objects for that matter:
import play.api.libs.json._
import play.api.libs.functional.syntax._
sealed trait Tree
case class Node(value: Double, child: Tree) extends Tree
case class Leaf(leaf: Double) extends Tree
val json: JsValue = Json.parse("""
{
"value": 1.0,
"child": {
"leaf": 2.0
}
}
""")
implicit val nReader = Json.reads[Node]
implicit val lReader = Json.reads[Leaf]
implicit lazy val treeReads: Reads[Tree] =
__.lazyRead(nReader).map(x => x:Tree) orElse __.lazyRead(lReader).map(x => x:Tree)
json.validate[Tree] match {
case s: JsSuccess[Tree] => {
val place: Tree = s.get
println(place)
}
case e: JsError => {
println(e)
}
}
https://scalafiddle.io/sf/sX8OkWI/13

How to edit existing JSON object with sprayJSON

I am using akka with spray json support for which I need to edit value in the recieved json.
import akka.http.scaladsl.server.Directives
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import spray.json._
final case class Item(name: String, id: Long)
final case class Order(items: List[Item],orderTag:String)
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
implicit val itemFormat = jsonFormat2(Item)
implicit val orderFormat = jsonFormat2(Order)
}
In my use case I recieve the json with orderTag value as null, all I need to do is edit the orderTag value with and then use it as entity value.Is it possible to write/edit jsonObject and How to do that ?
class MyJsonService extends Directives with JsonSupport {
// format: OFF
val route =
get {
pathSingleSlash {
complete(Item("thing", 42)) // will render as JSON
}
} ~
post {
entity(as[Order]) { order => // will unmarshal JSON to Order
val itemsCount = order.items.size
val itemNames = order.items.map(_.name).mkString(", ")
complete(s"Ordered $itemsCount items: $itemNames")
}
}
}
You can just edit the json AST like ..
val json = """{"orderTag":null}"""
val jsVal = json.parseJson
val updatedJs = if (jsObj.fields.get("orderTag") == Some(JsNull)) {
JsObject(jsObj.fields + ("orderTag" -> JsString("new tag")))
} else {
jsObj
}
updatedJs.compactPrint
res26: String = """
{"orderTag":"new tag"}
"""

Handle JSON data in POST request using json4s

I'm trying to map JSON i/p to my case class CacheRequest.
request is POST.
I'm new to Scala and Akka.
import org.json4s.{DefaultFormats, Formats}
implicit val formats: Formats = DefaultFormats
val route: Route = traceContextAwareRoute {
pathPrefix(system.name) {
post {
path("sample") {
entity(as[CacheRequest]) { x => {
val cacheRequest: CacheRequest = CacheRequest(x.a, x.b, x.c, x.d, x.e, x.f)
onComplete(getSystemStock(cacheRequest)) {
(response: Try[Option[CacheResponse]]) => complete(processResponse(response))
}
}
}
}
}
}
My case class is like this.
case class CacheRequest(a: String,
b: String,
c: Int,
d: Int,
e: Int,
f: Int)
Getting an Error Like
could not find implicit value for parameter um: akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[mcc.movie.common.model.CacheRequest]
not enough arguments for method as: (implicit um: akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[mcc.movie.common.model.CacheRequest])akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[mcc.movie.common.model.CacheRequest]
I'supposed to do this using json4s.
Any help regarding this is fine for me.
You can use this lib: https://github.com/hseeberger/akka-http-json
code may something like this
import org.json4s.{DefaultFormats, Formats, jackson}
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
class Route {
import Json4sSupport._
implicit val formats: Formats = DefaultFormats
implicit val serialization = jackson.Serialization
val route: Route = traceContextAwareRoute {
pathPrefix(system.name) {
post {
path("sample") {
entity(as[CacheRequest]) { x => {
val cacheRequest: CacheRequest = CacheRequest(x.a, x.b, x.c, x.d, x.e, x.f)
onComplete(getSystemStock(cacheRequest)) {
(response: Try[Option[CacheResponse]]) => complete(processResponse(response))
}
}
}
}
}
}
}

Json.writes[List[Foo]] as "map"

I have a case class Foo(bars: List[Bar]) who is rendered as json via Json inception as an object with an array :
{"bars": [
{
"key: "4587-der",
"value": "something"
}
]
}
But I want to render the bars: List[Bar] as a "map" where Bar.key is used as key :
{"bars":{
"4587-der": {
"value": "something"
}
}
}
How can I obtains that without modifying my case class Foo ?
Thanks a lot
You can define a Writes for Bar by extending Writes[Bar] and implementing a writes method for it:
case class Bar(key: String, value: String)
implicit object BarWrites extends Writes[Bar] {
def writes(bar: Bar): JsValue = Json.obj(
bar.key -> Json.obj("value" -> bar.value)
)
}
scala> Json.stringify(Json.toJson(Bar("4587-der", "something")))
res0: String = {"4587-der":{"value":"something"}}
For those that may be interested, here is a (somewhat) crude implementation of Reads[Bar]:
implicit object BarReads extends Reads[Bar] {
def reads(js: JsValue): JsResult[Bar] = js match {
case JsObject(Seq((key, JsObject(Seq(("value", JsString(value))))))) => JsSuccess(Bar(key, value))
case _ => JsError(Seq())
}
}
scala> Json.parse(""" [{"4587-der":{"value": "something"}}] """).validate[List[Bar]]
res11: play.api.libs.json.JsResult[List[Bar]] = JsSuccess(List(Bar(4587-der,something)),)
Edit, since the OP wants the Bars merged into an object rather than an array:
You'll also have to define a special Writes[List[Bar]] as well:
implicit object BarListWrites extends Writes[List[Bar]] {
def writes(bars: List[Bar]): JsValue =
bars.map(Json.toJson(_).as[JsObject]).foldLeft(JsObject(Nil))(_ ++ _)
}
scala> val list = List(Bar("4587-der", "something"), Bar("1234-abc", "another"))
list: List[Bar] = List(Bar(4587-der,something), Bar(1234-abc,another))
scala> Json.stringify(Json.toJson(list))
res1: String = {"4587-der":{"value":"something"},"1234-abc":{"value":"another"}}

Spray-json deserializing nested object

How to deserialize nested objects correctly in spray-json?
import spray.json._
case class Person(name: String)
case class Color(n: String, r: Int, g: Int, b: Int, p: Person)
object MyJsonProtocol extends DefaultJsonProtocol {
implicit object ColorJsonFormat extends RootJsonFormat[Color] {
def write(c: Color) = JsObject(
"color-name" -> JsString(c.n),
"Green" -> JsNumber(c.g),
"Red" -> JsNumber(c.r),
"Blue" -> JsNumber(c.b),
"person-field" -> JsObject("p-name" -> JsString(c.p.name))
)
def read(value: JsValue) = {
value.asJsObject.getFields("color-name", "Red", "Green", "Blue", "person-field") match {
case Seq(JsString(name), JsNumber(red), JsNumber(green), JsNumber(blue), JsObject(person)) =>
Color(name, red.toInt, green.toInt, blue.toInt, null) //gotta replace null with correct deserializer
case _ => throw new DeserializationException("Color expected")
}
}
}
}
import MyJsonProtocol._
val jsValue = Color("CadetBlue", 95, 158, 160, Person("guest")).toJson
jsValue.prettyPrint
val color = jsValue.convertTo[Color] //person is missing of course
On a side-note, how to spray-json help serializing to a map of fields (with nested map for nested objects)?
The example below demonstrates JSON -> Abstract Syntax Tree -> Scala Case Classes and back with custom field names and support for optional case class members. The example is derived from the spray-json documentation at https://github.com/spray/spray-json for version 1.2.5.
package rando
import spray.json._
case class Color(name: String, red: Int, green: Int, blue: Int)
case class Team(name: String, color: Option[Color])
object MyJsonProtocol extends DefaultJsonProtocol {
implicit val colorFormat = jsonFormat(Color, "name", "r", "g", "b")
implicit val teamFormat = jsonFormat(Team, "name", "jersey")
}
import MyJsonProtocol._
object GoSox extends App {
val obj = Team("Red Sox", Some(Color("Red", 255, 0, 0)))
val ast = obj.toJson
println(obj)
println(ast.prettyPrint)
println(ast.convertTo[Team])
println("""{ "name": "Red Sox", "jersey": null }""".asJson.convertTo[Team])
println("""{ "name": "Red Sox" }""".asJson.convertTo[Team])
}
The example outputs the following when executed:
Team(Red Sox,Some(Color(Red,255,0,0)))
{
"name": "Red Sox",
"jersey": {
"name": "Red",
"r": 255,
"g": 0,
"b": 0
}
}
Team(Red Sox,Some(Color(Red,255,0,0)))
Team(Red Sox,None)
Team(Red Sox,None)
To your remaining question - how to reuse JSON conversions within a wrapping type:
"person-field" -> JsObject("p-name" -> JsString(c.p.name))
I would change this to:
"person-field" -> p.toJson
This way, you are letting the Person case class JSONify itself, instead of introducing another way in the wrapping object. DRY and simpler.
And:
case Seq(JsString(name), JsNumber(red), JsNumber(green), JsNumber(blue), JsObject(person)) =>
Color(name, red.toInt, green.toInt, blue.toInt, null)
Use the .convertTo[Person] here:
case Seq(JsString(name), JsNumber(red), JsNumber(green), JsNumber(blue), jsv) =>
Color(name, red.toInt, green.toInt, blue.toInt, jsv.convertTo[Person])
If there are problems, please ask for more help. I have similar structure in my own project but didn't try to run them in this context.
import models.{Address, Patient}
import spray.json._
object Hospital {
object MyJsonProtocol extends DefaultJsonProtocol {
implicit val address = jsonFormat(Address, "country", "state", "zip")
implicit val patient = jsonFormat(Patient, "name", "regNumber", "address")
}
import MyJsonProtocol._
def main(args: Array[String]): Unit = {
val p1 = Patient(name = "Amar", regNumber = 234, address = Address("IN", "KA", 49))
println(p1.toJson.sortedPrint)
}
}
Output
{
"address": {
"country": "IN",
"state": "KA",
"zip": 49
},
"name": "Amar",
"regNumber": 234
}