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

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
}

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 can I send array of json objects in akka-http request

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

How to refactor this scala code?

I want to refactor this code into something more readable and better in general. I know that in Scala there are normally neat ways of doing things but for me it's getting a bit messy (BTW I'm using the Play library in the code). this is a snippet of my code:
class HomeController #Inject()
(cc: ControllerComponents)
(implicit val config: Configuration)
extends AbstractController(cc) {
def removeIdElement(uid: String) =
HAction(uid, "AuthEvent", 1, "login", parse.text).async {
implicit request: Request[String] =>
val promise = Promise[Result]()
Future {
val removeId = request.body.toLong
println(s"remove id $removeId")
promise completeWith {
idElementsDAO.remove(removeId, uid.toLong) map {
_ => Ok("")
} recover {
case t: Throwable =>
val errorMessage: String = getMessageFromThrowable(t)
println("remove id element failure " + errorMessage)
BadRequest(errorMessage)
}
}
} recover {
case t: Throwable =>
val errorMessage: String = getMessageFromThrowable(t)
println("remove id element failure " + errorMessage)
promise.success(BadRequest(errorMessage))
}
promise.future
}
}
Assuming that idElementsDAO.remove return a Future, this is probably more idiomatic:
def removeIdElement(uid: String) =
HAction(uid, "AuthEvent", 1, "login", parse.text).async {implicit request =>
val removeId = request.body.toLong
println(s"remove id $removeId")
idElementsDAO.remove(removeId, uid.toLong)
.map(_ => NoContent) // probably more correct than `Ok("")`
.recover {
case t: Throwable =>
val errorMessage: String = getMessageFromThrowable(t)
println("remove id element failure " + errorMessage)
BadRequest(errorMessage)
}
}
No need for the Promise or the call to Future {...} (Future.apply).
Keep in mind, it's probably not the best idea to directly pass the underlying error of any Throwable directly to the http client (browser?).
If you add generic error handling code to the global error handler (for unexpected errors) that logs the error and sends a generic message to the front-end, you can then write it even cleaner like this:
def removeIdElement(uid: String) =
HAction(uid, "AuthEvent", 1, "login", parse.text).async {implicit request =>
val removeId = request.body.toLong
println(s"remove id $removeId")
for {
_ <- idElementsDAO.remove(removeId, uid.toLong)
} yield NoContent
}
https://www.playframework.com/documentation/2.6.x/ScalaErrorHandling
Here is a simpler version of your code:
class HomeController #Inject()(cc: ControllerComponents)(implicit val config: Configuration)
extends AbstractController(cc) {
def removeIdElement(uid: String) = HAction(uid, "AuthEvent", 1, "login", parse.text).async {
implicit request: Request[String] =>
Future {
val removeId = request.body.toLong
println(s"Removing id $removeId")
removeId
}.flatMap(id => idElementsDAO.remove(id, uid.toLong))
.map(_ => Ok(""))
.recover {
case t: Throwable =>
val errorMessage = getMessageFromThrowable(t)
println(s"Removing id element failed: ${errorMessage}")
BadRequest(errorMessage)
}
}
}
In the above code, a Promise is not needed, and the recover combinator is not repeated.

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.

required: spray.httpx.marshalling.ToResponseMarshallable Error

Hey im pretty new to Spray and reactive mongo .
Im trying to return a list of result as json but i'm having some issue with converting the result to list of json.
this is my model
import reactivemongo.bson.BSONDocumentReader
import reactivemongo.bson.BSONObjectID
import reactivemongo.bson.Macros
case class Post(id: BSONObjectID, likes: Long, message: String, object_id: String, shares: Long)
object Post {
implicit val reader: BSONDocumentReader[Post] = Macros.reader[Post]
}
the Mongo method
def getAll(): Future[List[Post]] ={
val query = BSONDocument(
"likes" -> BSONDocument(
"$gt" -> 27))
collection.find(query).cursor[Post].collect[List]()
}
and this is the route
val route1 =
path("posts") {
val res: Future[List[Post]]= mongoService.getAll()
onComplete(res) {
case Success(value) => complete(value)
case Failure(ex) => complete(ex.getMessage)
}
}
error
type mismatch; found : List[com.example.model.Post] required: spray.httpx.marshalling.ToResponseMarshallable
thanks,
miki
You'll need to define how a Post will be serialized, which you can do via a spray-json Protocol (see the docs for more detailed information). It's quite easy to do so, but before that, you'll also need to define a format for the BSONObjectId type, since there's no built-in support for that type in spray-json (alternatively, if object_id is a string representation of the BSONObjectId, think about removing the id property from your Post class or change it to be a String):
// necessary imports
import spray.json._
import spray.httpx.SprayJsonSupport._
implicit object BSONObjectIdProtocol extends RootJsonFormat[BSONObjectID] {
override def write(obj: BSONObjectID): JsValue = JsString(obj.stringify)
override def read(json: JsValue): BSONObjectID = json match {
case JsString(id) => BSONObjectID.parse(id) match {
case Success(validId) => validId
case _ => deserializationError("Invalid BSON Object Id")
}
case _ => deserializationError("BSON Object Id expected")
}
}
Now, we're able to define the actual protocol for the Post class:
object PostJsonProtocol extends DefaultJsonProtocol {
implicit val format = jsonFormat5(Post.apply)
}
Furthermore, we'll also need to make sure that we have the defined format in scope:
import PostJsonProtocol._
Now, everything will compile as expected.
One more thing: have a look at the docs about the DSL structure of spray. Your mongoService.getAll() isn't within a complete block, which might not reflect your intentions. This ain't an issue yet, but probably will be if your route get more complex. To fix this issue simply put the future into the onComplete call or make it lazy:
val route1 =
path("posts") {
onComplete(mongoService.getAll()) {
case Success(value) => complete(value)
case Failure(ex) => complete(ex.getMessage)
}
}