How can I send array of json objects in akka-http request - scala

I can handle {"phone": "2312323", "message": "This is test"} in Akka-Http using
entity(as[Message]) { message =>
val f: Future[Any] = service ask message
onSuccess(f) { r: Any =>
{
r match {
case Some(message: Message) =>
complete(HttpEntity(ContentTypes.`application/json`, message.toJson.prettyPrint))
case _ =>
complete(StatusCodes.NotFound)
}
}
}
}
but how can I handle
[
{"phone": "2312323", "message": "This is test"},
{"phone": "2312321213", "message": "This is test 1212"}
]

Hi You can do like this:
import akka.http.scaladsl.server.Directives
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import spray.json._
// domain model
final case class Info(phone: String, message: String)
final case class MessageInfo(messages: List[Info])
// collect your json format instances into a support trait:
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
implicit val itemFormat = jsonFormat2(Info)
implicit val orderFormat = jsonFormat1(MessageInfo) // contains List[Info]
}
// use it wherever json (un)marshalling is needed
class MyJsonService extends Directives with JsonSupport {
// format: OFF
val route =
post {
entity(as[MessageInfo]) { messageInfo => // will unmarshal JSON to Order
val messageCount = messageInfo.messages.size
val phoneNumbers = messageInfo.items.map(_.phone).mkString(", ")
complete(s"Messages $messageCount with the contact number: $phoneNumbers")
}
}
// format: ON
}
For more info please see here:
https://doc.akka.io/docs/akka-http/current/common/json-support.html

You can use a simpler way, with less code (and use gson):
val gson = new Gson
...
entity(as[String]) { messageStr =>
val f: Future[Any] = service ask message
onSuccess(f) { r: Any =>
{
r match {
case Some(messageStringValue: String) =>
val messagesList = gson.fromJson(messageStringValue, classOf[Array[Message]])
val messageCount = messagesList.size
val phoneNumbers = messagesList.map(_.phone).mkString(", ")
complete(s"Messages $messageCount with the contact number: $phoneNumbers")
case _ =>
complete(StatusCodes.NotFound)
}
}
}
}

Related

Comparing the json data types at runtime using Jackson and Scala

I have an incoming JSON data that looks like below:
{"id":"1000","premium":29999,"eventTime":"2021-12-22 00:00:00"}
Now, I have created a class that will accept this record and will check whether the data type of the incoming record is according to the data types defined in the case class. However, when I am calling the method it is always calling the Failure part of the match case.
case class Premium(id: String, premium: Long, eventTime: String)
class Splitter extends ProcessFunction[String, Premium] {
val outputTag = new OutputTag[String]("failed")
def fromJson[T](json: String)(implicit m: Manifest[T]): Either[String, T] = {
Try {
println("inside")
lazy val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
mapper.readValue[T](json)
} match {
case Success(x) => {
Right(x)
}
case Failure(err) => {
Left(json)
}
}
}
override def processElement(i: String, context: ProcessFunction[String, Premium]#Context, collector: Collector[Premium]): Unit = {
fromJson(i) match {
case Right(data) => {
collector.collect(data)
println("Good Records: " + data)
}
case Left(json) => {
context.output(outputTag, json)
println("Bad Records: " + json)
}
}
}
}
Based on the sample record above, it should pass the Success value but no matter what I pass, it always enters the Failure part. What else is missing?
I am using Scala 2.11.12 and I tried examples from this link and this link but no luck.

How to parse dynamic JSON with Circe

I'm trying to parse JSON where same field can be either array or object. Same as, specific field can be either string or number. Please consider examples below.
Empty object
{
"technicalData": {}
}
Collection with field being either string or number
{
"technicalData": [
{
"techValueString": "0.173"
},
{
"techValueString": 0.173
}
]
}
How can I do it with Circe mapping to Scala classes accepting Nil when data is {}?
case class Response(technicalData: Seq[TechnicalData])
case class TechnicalData(techValueString: String)
Thanks.
Here is less verbose solution applying Circe's Decoders
case class Response(technicalData: Seq[TechnicalData])
case class TechnicalData(techValueString: String)
class StringToResponse() extends (String => Response) {
implicit val responseDecoder: Decoder[Response] = Decoder.instance { c =>
for {
technicalData <- c.downField("technicalData").focus match {
case None => Right(Nil)
case Some(seq) => seq.asArray match {
case None => Right(Nil)
case Some(_) => c.get[Seq[TechnicalData]]("technicalData")
}
}
} yield {
Response(technicalData)
}
}
implicit val technicalDataDecoder: Decoder[TechnicalData] = (
Decoder.instance(_.get[String]("techValueString")).or(
Decoder.instance(_.get[Double]("techValueString").map(_.toString))
)
) mapN TechnicalData
override def apply(body: String): Response = {
decode[Response](body) match {
case Right(response) => response
case Left(e) => throw new RuntimeException(e)
}
}
}
Hope this will help someone who would come across similar problem.
This is a really verbose way of resolving your problem but I hope it has the advantage of letting you identify, or even rectify, every limit cases, which you might need:
import io.circe._
import io.circe.parser.parse
case class Response(technicalData: Seq[TechnicalData])
case class TechnicalData(techValueString: String)
val stringAsJson1 = """{
"technicalData": {}
}"""
val stringAsJson2 = """{
"technicalData": [
{
"techValueString": "0.173"
},
{
"techValueString": 0.173
}
]
}"""
def manageTechnicalDataAsArray(jsonArray: Vector[io.circe.Json]): Response = {
Response(
jsonArray.map(cell => {
val value = cell.asObject
.getOrElse(throw new Exception("technicalData as a array should have each cell as an object"))
.apply("techValueString")
.getOrElse(throw new Exception("techValueString should be a key of any cell under technicalData array"))
TechnicalData(value.asNumber
.map(_.toString)
.getOrElse(
value.asString
.getOrElse(throw new Exception("techValueString value should be either string or number"))
)
)
}
)
)
}
def manageTechnicalDataAsObject(jsonObject: io.circe.JsonObject): Response = {
jsonObject.toIterable match {
case empty if empty.isEmpty => Response(Nil)
case _ => throw new Exception("technicalData when object should be empty")
}
}
def parseResponse(jsonAsString: String): Response = {
parse(jsonAsString).getOrElse(Json.Null)
.asObject
.map(_("technicalData")
.getOrElse(throw new Exception("the json should contain a technicalData key"))
.arrayOrObject(throw new Exception("technicalData should contain either an objet or array"),
manageTechnicalDataAsArray,
manageTechnicalDataAsObject
)
).getOrElse(throw new Exception("the json should contain an object at top"))
}
println(parseResponse(stringAsJson1))
println(parseResponse(stringAsJson2))
I might come with a shorter version soon but less indicative on limit cases. You can explore them with tweaked version of a good json of yours.
Hope it helps.
EDIT: Here is a shorter and cleaner solution than above, which come after #Sergey Terentyev well found one. Well, it might be less readeable somehow, but it tends to do the same thing with more or less way to handle limit cases:
// Structure part
case class TechnicalData(techValueString: String)
object TechnicalData {
def apply[T](input: T) = new TechnicalData(input.toString)
}
case class Response(technicalData: Seq[TechnicalData])
// Decoding part
import io.circe.{Decoder, parser, JsonObject, JsonNumber}
import io.circe.Decoder.{decodeString, decodeJsonNumber}
def tDDGenerator[C : Decoder]: Decoder[TechnicalData] = Decoder.forProduct1("techValueString")(TechnicalData.apply[C])
implicit val technicalDataDecoder: Decoder[TechnicalData] = tDDGenerator[String].or(tDDGenerator[JsonNumber])
implicit val responseDecoder: Decoder[Response] = Decoder[JsonObject]
.emap(_("technicalData").map(o => Right(o.as[Seq[TechnicalData]].fold(_ => Nil, identity)))
.getOrElse(Right(Nil))
.map(Response.apply))
// Test part
val inputStrings = Seq(
"""{
| "technicalData": [
| {
| "techValueString": "0.173"
| },
| {
| "techValueString": 0.173
| }
| ]
|}
""".stripMargin,
"""{
| "technicalData": {}
|}
""".stripMargin
)
inputStrings.foreach(parser.decode[Response](_).fold(println,println))

How can I serialize Sangria responses with json4s and Akka HTTP?

I'm working through a slight variation of Sangria's Getting Started, using Akka HTTP. I'm attempting to use json4s-jackson as the serializaltion lib, but am running in to some trouble getting the response I want.
Specifically, the serialized response I get is the JSON version of the (StatusCode, Node) tuple:
{
"_1": {
"allowsEntity": true,
"defaultMessage": "OK",
"intValue": 200,
"reason": "OK"
},
"_2": {
"data": {
"foo": {
"id": "1",
"name": "Foo"
}
}
}
}
The data portion is correct, but obviously I just want that and not the first element of the serialized tuple.
I'm using akka-http-json4s, so my trait with route looks like:
case class GraphQlData(query: String, operation: Option[String])
trait FooController {
import de.heikoseeberger.akkahttpjson4s.Json4sSupport._
implicit val serialization = jackson.Serialization
implicit val formats = DefaultFormats
val fooRoutes = post {
entity(as[GraphQlData]) { data =>
QueryParser.parse(data.query) match {
// query parsed successfully, time to execute it!
case Success(queryAst) =>
complete {
Executor
.execute(
SchemaDefinition.FooSchema,
queryAst,
new FooService,
operationName = data.operation
)
.map(OK -> _)
.recover {
case error: QueryAnalysisError => BadRequest -> error.resolveError
case error: ErrorWithResolver => InternalServerError -> error.resolveError
}
}
// can't parse GraphQL query, return error
case Failure(error) =>
complete(BadRequest -> error.getMessage)
}
}
}
implicit def executionContext: ExecutionContext
}
For the life of me I can't figure out what's wrong. I've been looking at sangria-akka-http-example but it seems to be exactly the same, with the exception of using spray-json instead of json4s.
Ideas? Thanks!
Ah, figured it out. I neglected to add
import sangria.marshalling.json4s.jackson._
to the trait defining the route. Adding it does the trick.
Just wanted to provide a quick update to this answer for the full GraphQLRequest. Now the variables are included in the request.
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import org.json4s._
import org.json4s.JsonAST.JObject
import sangria.marshalling.json4s.jackson._
case class GQLRequest(query: String, operationName: Option[String], variables: JObject)
trait SomeJsonSupport extends Json4sSupport {
implicit val serialization = jackson.Serialization
implicit val formats = DefaultFormats
}
trait GraphQLResource extends SomeJsonSupport{
implicit val timeout:Timeout
implicit val system:ActorSystem
import system.dispatcher
def graphqlRoute: Route =
(post & path("graphql")) {
entity(as[GQLRequest]) { requestJson =>
println(s"This is the requestJson = $requestJson")
graphQLEndpoint(requestJson)
}
} ~
get {
println(s"This is working")
getFromResource("graphiql.html")
}
def graphQLEndpoint(requestJson: GQLRequest): Route = {
val route = QueryParser.parse(requestJson.query) match {
case Success(query) =>
println(s"This is the query $query")
val vars = requestJson.variables match {
case jObj:JObject => jObj
case _ => JObject(List.empty)
}
val futureJValue = Executor.execute(clientSchema,
query,
NclhGqlRequest(this),
operationName = requestJson.operationName,
variables = vars)
val futureTupleStatusCodeJValue = futureJValue.map(OK -> _).recover {
case error: QueryAnalysisError => BadRequest -> error.resolveError
case error: ErrorWithResolver => InternalServerError -> error.resolveError
}
complete(futureTupleStatusCodeJValue)
case Failure(error) =>
complete(BadRequest, error.getMessage)
}
route
}

How to dynamically select class type when parsing JSON to object in Play Framework?

The following sample code uses Play Framework to parse JSON to an object:
def createEvent(): Action[JsValue] = Action.async(parse.tolerantJson) {
request => {
request.body.validate[SomeEvent] match {
case o:JsSuccess[SomeEvent] => {
//do something
Future(Created)
}
}
}
}
Is it possible to generalise it so it can handle different event types? e.g.
def createEvent(): Action[JsValue] = Action.async(parse.tolerantJson) {
request => {
val eventType = request.contentType match {
case Some("SomeEventType") => SomeEvent
case Some("OtherEventType") => OtherEvent
}
request.body.validate[eventType] match {
case o:JsSuccess[eventType] => {
//do something
Future(Created)
}
}
}
}
Currently, the above code will fail in the line request.body.validate[eventType]
You can extract body.validate[T] into a function and call it from your patten matching construct with a proper type, i.e:
def extract[T: JsonFormat](implicit req: Request[AnyContent]) = req.body.valudate[T]
request.contentType match {
case Some("SomeEventType") => extract[SomeEvent]
case Some("OtherEventType") => extract[OtherEvent]
}
You can read and create class from contentType dynamically. But you can have problem, if will be no implicit Format for extracted type in scope:
error: No Json formatter found for type A. Try to implement an implicit Format for this type.
or
java.lang.ClassNotFoundException: models.Bazz
The available data:
model
package models
case class Bar(name: String)
request
request.contentType: Option[String] = Some("models.Bar")
incoming body
request.body: JsValue = """{"name": "bar name"}"""
format[Bar]
implicit val BarFormat = Json.format[models.Bar]
extractor:
def extract[A <: Any](ct: String, json: JsValue)(implicit format: Format[A]) = {
val manifest:Manifest[A] = Manifest.classType[A](Class.forName(ct))
json.validate[A]
}
request.contentType.map(extract(_, request.body))
res1: Option[play.api.libs.json.JsResult[models.Bar]] = Some(JsSuccess(Bar(bar name),/name))
you can see https://github.com/strongtyped/fun-cqrs/blob/demos/shop-sample/samples/shop/src/main/scala/funcqrs/json/TypedJson.scala for other way of deserializing self-contained json.

How to feed JSON to CASE CLASS directly using functional programming in scala?

{
"cars": [{
"amount": 120.00,
"name": "Car1"
}, {
"amount": 245.00,
"name": "Car2"
}]
}
I am reading above JSON as following in my Controller
val body: JsObject = request.body.asInstanceOf[JsObject]
I am having following CASE CLASS
case class BIC(name: String, amount: Double)
I want to create List[BIC] objects by reading data from JSON [e.g. body] using Functional style
Use Play JSON.
Example:
case class Wrapper(cars: List[Bic])
case class BIC(name: String, amount: Double)
Then in your controller:
implicit val wrapperFormats = Json.format[Wrapper]
implicit val bICFormats = Json.format[BIC]
def postCars(): Action[JsValue] = Action(json.parse) { implicit request =>
request.body.validate[Wrapper] match {
case JsSuccess(obj, _) => {
//do something with obj.
}
case JsError(err) => {
BadRequest(
JsObject(
"error" -> err.toString
)
)
}
}
}
Please note that I am returning Action[JsValue] this is so JQuery will run success when using AJAX.
I hope this helps,
Rhys
another reference:
https://www.playframework.com/documentation/2.5.x/ScalaJsonCombinators
First, define two case classes for your model like this :
object Models {
case class Bic(name : String, amount : Double)
object Bic {
implicit val BicFormat = Json.format[Bic]
}
case class Cars(bics : List[Bic])
object Cars {
implicit val CarsFormat = Json.format[Cars]
}
}
You're using the Play Framework so you can use the JSON library.
In your controller, if you want to read the bics, you can do it like that :
def getCars = Action(parse.json) { request =>
request.body.validate[Cars] map { cars =>
// treat your cars ..
}
}