I'm just starting out with Akka HTTP and I'm having a bit of trouble with the routing DSL and marshaling. The tilde in the 'route' setup results in the error:
value ~ is not a member of akka.http.scaladsl.server.RequestContext ⇒
scala.concurrent.Future[akka.http.scaladsl.server.RouteResult]
possible cause: maybe a semicolon is missing before 'value ~'?
Also, the JSON marshaling in the 'get' clause causing the error:
◾Cannot find JsonWriter or JsonFormat type class for
scala.collection.immutable.Map[String,scala.collection.mutable.Map[String,Tweet]]
◾not enough arguments for method toJson: (implicit writer:
spray.json.JsonWriter[scala.collection.immutable.Map[String,scala.collection> .mutable.Map[String,Tweet]]])spray.json.JsValue.
Unspecified value parameter writer.
I've followed the documentation examples fairly closely, so I'd appreciate help in understanding these errors and how to resolve them. Thanks.
API
import akka.actor.ActorSystem
import scala.concurrent.Future
import akka.stream.ActorMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives.path
import akka.http.scaladsl.server.Directives.pathPrefix
import akka.http.scaladsl.server.Directives.post
import akka.http.scaladsl.server.Directives.get
import akka.http.scaladsl.server.Directives.complete
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import akka.http.scaladsl.server.Directives.{entity, as}
import akka.http.scaladsl.model.StatusCodes.{Created, OK}
import spray.json._
import DefaultJsonProtocol._
import akka.stream.Materializer
import scala.concurrent.ExecutionContext
trait RestApi {
import TweetProtocol._
import TweetDb._
implicit val system: ActorSystem
implicit val materializer: Materializer
implicit val execCtx: ExecutionContext
val route =
pathPrefix("tweets") {
(post & entity(as[Tweet])) { tweet =>
complete {
Created -> Map("id" -> TweetDb.save(tweet)).toJson
}
} ~
(get) {
complete {
OK -> Map("tweets" -> TweetDb.find()).toJson
}
}
}
}
object TweetApi extends App with RestApi {
implicit val system = ActorSystem("webapi")
implicit val materializer = ActorMaterializer()
implicit val execCtx = system.dispatcher
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
Console.readLine()
bindingFuture.flatMap(_.unbind()).onComplete { _ => system.shutdown() }
}
Protocol
import spray.json.DefaultJsonProtocol
case class Tweet(author: String, body: String)
object TweetProtocol extends DefaultJsonProtocol {
implicit val TweetFormat = jsonFormat2(Tweet.apply)
}
Pseudo-database
import scala.collection.mutable.Map
object TweetDb {
private var tweets = Map[String, Tweet]()
def save(tweet: Tweet) = {
val id: String = java.util.UUID.randomUUID().toString()
tweets += (id -> tweet)
id
}
def find() = tweets
def findById(id: String) = tweets.get(id)
}
For your 1st error, try the suggestion from the comment, ie. import all from Directives
For second part
◾Cannot find JsonWriter or JsonFormat type class for scala.collection.immutable.Map[String,scala.collection.mutable.Map[String,Tweet]]
◾not enough arguments for method toJson: (implicit writer: spray.json.JsonWriter[scala.collection.immutable.Map[String,scala.collection> .mutable.Map[String,Tweet]]])spray.json.JsValue. Unspecified value parameter writer.
You need to define JsonFormat for Map[String, mutable.Map[String, Tweet]]
By creating an object in your TweetProtocol, extending RootJsonFormat
eg.
type DBEntry = Map[String, mutable.Map[String, Tweet]]
object TweetProtocol extends DefaultJsonProtocol {
implicit object DBEntryJsonFormat extends RootJsonFormat[DBEntry] {
override def read(json: JSValue) {
// your implementation
}
override def write(dbEntry: DBEntry) {
// implementation
}
}
}
Related
I am writing tests for my case classes and in the following test I have a StackOverflowError:
test("ValuationRequest - Conversion between case object and Json works") {
val caseObject = ValuationRequest(TIME_SERIES_INTRADAY, "", IntraDayIntervals.MIN_5)
val jsonString = caseObject
.asJson
.printWith(Printer.noSpaces)
decode[ValuationRequest](jsonString) must be(Right(caseObject))
}
here's the stacktrace:
An exception or error caused a run to abort.
java.lang.StackOverflowError
at io.circe.syntax.package$EncoderOps$.asJson$extension(package.scala:10)
at eventbus.ValuationRequest$.$anonfun$encodeRequest$1(ValuationRequestCases.scala:14)
....
This is the case code:
import AV_Enums.TimeSeriesFunctions.TIME_SERIES_INTRADAY
import AV_Enums.{IntraDayInterval, IntraDayIntervals, TimeSeriesType}
import cats.syntax.functor._
import io.circe.generic.auto._
import io.circe.generic.semiauto._
import io.circe.syntax._
import io.circe.{Decoder, Encoder}
case class ValuationRequest(function: TimeSeriesType = TIME_SERIES_INTRADAY, symbol: String, interval: IntraDayInterval = IntraDayIntervals.MIN_5)
object ValuationRequest {
implicit val encodeRequest: Encoder[ValuationRequest] = Encoder.instance { case response#ValuationRequest(_, _, _) => response.asJson }
implicit val decodeRequest: Decoder[ValuationRequest] = deriveDecoder[ValuationRequest].widen
}
These are the enums it uses:
sealed abstract class TimeSeriesType(val text: String) extends EnumEntry {}
sealed abstract class IntraDayInterval(val text: String) extends EnumEntry {}
object TimeSeriesFunctions extends Enum[TimeSeriesType] with CirceEnum[TimeSeriesType] {
val values: immutable.IndexedSeq[TimeSeriesType] = findValues
case object TIME_SERIES_INTRADAY extends TimeSeriesType("TIME_SERIES_INTRADAY")
case object TIME_SERIES_DAILY extends TimeSeriesType("TIME_SERIES_DAILY")
case object TIME_SERIES_WEEKLY extends TimeSeriesType("TIME_SERIES_WEEKLY")
case object TIME_SERIES_MONTHLY extends TimeSeriesType("TIME_SERIES_MONTHLY")
}
object IntraDayIntervals extends Enum[IntraDayInterval] with CirceEnum[IntraDayInterval] {
val values: immutable.IndexedSeq[IntraDayInterval] = findValues
case object MIN_1 extends IntraDayInterval("1min")
case object MIN_5 extends IntraDayInterval("5min")
case object MIN_15 extends IntraDayInterval("15min")
case object MIN_30 extends IntraDayInterval("30min")
case object MIN_60 extends IntraDayInterval("60min")
}
I don't understand what is going on with this case, all the others work fine and are implemented the same way. Can anyone help?
Problematic line
Encoder.instance { case response#ValuationRequest(_, _, _) => response.asJson }
.asJson here requires Encoder[ValuationRequest] which is a recursive call. Any reason you cannot use deriveEncoder[ValuationRequest]?
Scalatra Code:
import org.scalatra._
import org.json4s.{DefaultFormats, Formats}
import org.scalatra.json._
class AppServlet extends AppStack with JacksonJsonSupport{
protected implicit lazy val jsonFormats: Formats = DefaultFormats
private def generateJSON():((String, String),(String, String)) = {
val json = ("Firstname" -> "joe", "LastName" -> "cole")
json
}
before() {
contentType = formats("json")
}
get("/") {
generateJSON
}
}
I am trying to return simple json using scalatra framework and the output is something like this {"_1":{"Firstname":"joe"},"_2":{"LastName":"cole"}}. I dont need _1 or _2 to be printed. Please note i am not trying to return any objects. I just need to make my own json and then return it. It is not associated with any data model. Any help is appreciated.
What you create is a tuple of (String, String), that's not surprising the output is like that. You should either create a case class, or, since you use json4s, return:
// don't forget this:
// import org.json4s.JsonDSL._
("Firstname" -> "joe") ~ ("LastName" -> "cole")
import org.scalatra._
import org.json4s.{DefaultFormats, Formats}
import org.scalatra.json._
import org.json4s._
import org.json4s.JsonDSL._
class AppServlet extends AppStack with JacksonJsonSupport{
protected implicit lazy val jsonFormats: Formats = DefaultFormats
private def generateJSON():JObject = {
val json = ("Firstname" -> "joe") ~ ("LastName" -> "cole")
json
}
get("/") {
generateJSON
}
}
I spent a whole day figuring out how to
Consume a remote restful json service
Unmarshall (deserialize) the payload into a case class model
The guys over at the google plus Akka mailing list were very helpful, so I thought it would be nice to have a working example on SO as well, for future reference.
Akka-Http 10.0.5
package xxx
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.model._
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.stream.{ActorMaterializer, Materializer}
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.{BeforeAndAfter, FlatSpec, MustMatchers}
import org.scalatest.mock.MockitoSugar
import spray.json.DefaultJsonProtocol
import scala.concurrent.{Await, ExecutionContextExecutor, Future}
import akka.http.scaladsl.server.Directives
case class ColorBlob(url: String, averageColor: String, classificationColor: String)
case class ColorBlobsResponse(colorBlobs: Map[String, Option[ColorBlob]])
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
implicit val format1 = jsonFormat3(ColorBlob)
implicit val format2 = jsonFormat1(ColorBlobsResponse)
}
class ColorBlobRestTest
extends FlatSpec
with MockitoSugar
with BeforeAndAfter
with MustMatchers
with ScalaFutures
with JsonSupport
with Directives {
implicit val system: ActorSystem = ActorSystem()
implicit val executor: ExecutionContextExecutor = system.dispatcher
implicit val materializer: Materializer = ActorMaterializer()
"this" should "work" in {
val request = HttpRequest(method = HttpMethods.GET, uri = s"https://colorservice/colorblobs/en?productCodes=904655")
val futureResponse: Future[HttpResponse] = Http().singleRequest(request)
val futureColorBlobResponse: Future[ColorBlobsResponse] = futureResponse.flatMap { response: HttpResponse =>
val entity: ResponseEntity = response.entity
Unmarshal(entity).to[ColorBlobsResponse]
}
import scala.concurrent.duration._
val colorBlobsResponse: ColorBlobsResponse = Await.result(futureColorBlobResponse, 1000.millis)
assert(1==1)
}
}
I would like to build a simple stub server with several routes using Http AKKA.
I have the following case class:
case class Person(name: String, age: Int)
object Person {
implicit def cardJsonFormat: RootJsonFormat[Person] = jsonFormat2(Person.apply)
}
How can I return this case class as a JSON response.
My route looks like:
case class Person(name: String, age: Int)
def route =
get {
path("person") {
complete {
}
}
}
You should try something like this:
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.server.Directives._
import spray.json.DefaultJsonProtocol
case class Person(name: String, age: Int)
case object Person extends SprayJsonSupport with DefaultJsonProtocol {
implicit val personFormat = jsonFormat2(Person.apply)
}
object PersonRoute {
val route =
get {
path("person") {
complete {
Person("Pawel", 25)
}
}
}
}
More details and examples can be found in the docs.
I found the following library akka-http-json used with json4s useful. It helps me cut down on the number of jsonFormatX statements. Every jsonFormatX statement is needed for every data type that needs to be marshalled / unmarshalled.
Just mix in the following trait where the marshall / unmarshall needs to occur:
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import org.json4s.jackson
trait JsonCodec extends Json4sSupport {
import org.json4s.DefaultFormats
import org.json4s.ext.JodaTimeSerializers
implicit val serialization = jackson.Serialization
implicit val formats = DefaultFormats ++ JodaTimeSerializers.all
}
I'm on a quest to create a JSON API where some of the models could be nicely generalized. I'm a newbie in Spray, so I started out a spike with an over simplified example.
However I can't figure out what is going on with the bellow code...
I have imported both
my custom implicits and
spray.httpx.SprayJsonSupport._
As I understand this is what I have to do in order to have an implicit in scope that can convert from JsonFormat to Marshaller.
Compiler error:
TestService.scala:15: could not find implicit value for parameter um: spray.httpx.unmarshalling.FromRequestUnmarshaller[my.company.Test[my.company.X]]
Code:
package my.company
import spray.routing.HttpService
import spray.json.{JsValue, JsObject, JsonFormat, DefaultJsonProtocol}
trait TestService extends HttpService {
import my.company.TestImplicits._
import spray.httpx.SprayJsonSupport._
val test =
path("test") {
post {
entity(as[Test[X]]) {
test => {
complete(s"type: ${test.common}")
}
}
}
}
}
trait Common {
def commonData: String
}
case class X(id: Long, commonData: String) extends Common
case class Y(commonData: String) extends Common
case class Test[T <: Common](comment: String, common: T)
object TestImplicits extends DefaultJsonProtocol {
implicit val xFormat = jsonFormat2(X)
implicit val yFormat = jsonFormat1(Y)
implicit val yTestFormat: JsonFormat[Test[Y]] = new JsonFormat[Test[Y]] {
def write(test: Test[Y]) = JsObject()
def read(js: JsValue) = Test("test", Y("y"))
}
implicit val xTestFormat: JsonFormat[Test[X]] = new JsonFormat[Test[X]] {
def write(test: Test[X]) = JsObject()
def read(js: JsValue) = Test("test", X(1L, "y"))
}
}
I would appreciate any help. Thanks in advance.
SOLVED
Solution was (as #jrudolp suggested) both to:
Move implicit definitions on top of the file (surprising)
Create RootJsonFormat rather than JsonFormat.