I'm currently going through the akka-http documentation & examples. I'm stuck on probably something very trivial: passing in route-configuration with Directives and JsonSupport. I'm following the docs on introduction and json-support
For the JsonSupport I need to make a class extending akka.http.scaladsl.server.Directives:
class RouteDef extends Directives with JsonSupport {
with JsonSupport defined by me:
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
How can I now use this class in:
val bindingFuture = Http().bindAndHandle(new RouteDef().route, "localhost", 8082)
The json marshalling is not working, as the jsonsupport is not wired on the route val (I suspect).
A separate question: What is the status of spray-json? Spray is not supported anymore, will spray-json still be maintained or replace by Jackson for example?
I tried the example on the json-support page, and it works as expected.
// domain model
final case class Item(name: String, id: Long)
final case class Order(items: List[Item])
// collect your json format instances into a support trait:
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
implicit val itemFormat = jsonFormat2(Item)
implicit val orderFormat = jsonFormat1(Order) // contains List[Item]
}
// use it wherever json (un)marshalling is needed
class MyJsonService extends Directives with JsonSupport {
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")
}
}
}
object Main extends App {
implicit val system = ActorSystem("main")
implicit val materializer = ActorMaterializer()
Http().bindAndHandle(new MyJsonService().route, "localhost", 8080)
}
And the result is :
~❯ curl http://127.0.0.1:8080/
{"name":"thing","id":42}%
~❯ curl -H "Content-Type: application/json" -X POST -d '{"items":[{"name":"thing2","id":43}]}' http://localhost:8080
Ordered 1 items: thing2%
So it should work unless you missed something like implicitly define system or materializer. In routing-dsl overview it described as follow:
The conversion from Route to flow can either be invoked explicitly using Route.handlerFlow or, otherwise, the conversion is also provided implicitly by RouteResult.route2HandlerFlow.
If this is the problem, maybe you should check that doc as well.
For spray-json, I don't know if it will be maintained. But as it's a lightweight JSON implementation and quite stable now, it's not likely to have a big change in the future.
And ofcoures, if you want to use a Jackson marshaler, it's not so difficult to create your own, like this.
Related
I have a simplified version of a Controller that looks like this (see the full file here):
#Singleton
class Application #Inject() (implicit
indexView: views.html.index,
deadbolt: DeadboltActions,
userService: UserService) extends InjectedController with I18nSupport {
import scala.concurrent._
def index =
TryCookieAuthAction { implicit jContext =>
deadbolt.WithAuthRequest()() { implicit request =>
Future {
implicit val lang = request.acceptLanguages.head
Ok(indexView(userService))
}
}
}
}
The issue I'm having is that if I comment the explicit extraction of lang then the view won't see it. Why is that? It used to work before Play 2.6 ...
Messages docs state that
...you can wrap a given Lang together with the MessagesApi to
create a play.api.i18n.Messages instance.
so consider refactoring views to take
implicit messages: Messages
instead of
implicit messagesApi: MessagesApi, lang: Lang
This way we do not have to explicitly handle Lang, because I18nSupport.request2messages implicit conversion will take care of that under the hood.
For example, say we have the following view/index.scala.html
#()(implicit messages: Messages)
#messages("salutation")
and and the following English externalised messages conf/messages.en
salutation=live long and prosper
and the following Italian externalised messages conf/messages.it
salutation=vivi a lungo ed in prosperita
and the following allowed languages conf/application.conf
play.i18n.langs = [ "en", "it" ]
then the controller does not need to explicitly handle Lang as long as we extend I18nSupport and have an implicit request in scope like so
#Singleton
class HomeController #Inject()(cc: ControllerComponents)
extends AbstractController(cc) with I18nSupport {
def index = Action { implicit request =>
Ok(views.html.index())
}
}
Now HTTP request with Accept-Language: it header should respond with
vivi a lungo ed in prosperita
whilst one with Accept-Language: en with
live long and prosper
Also consider I18N API Migration guide.
I have an Akka HTTP server with routing defined like this:
case class FooResults(results: Seq[Tuple2[String, Tuple2[Double, Double]]])
object MainApp extends App with JsonSupport {
...
lazy val routes: Route =
pathPrefix("foo") {
pathEnd {
get {
entity(as[String]) { str =>
val results =
(fooActor ? Foo(str)).mapTo[FooResults]
complete(results)
}
}
}
}
...
And in the class I have injected the implicit json support:
trait JsonSupport extends SprayJsonSupport {
import DefaultJsonProtocol._
implicit val userFormat = jsonFormat1(FooResults)
}
Somehow sbt still reports with
Type mismatch - FooResults with ToResponseMashallable
Anyone had similar problems? Thanks!
I figured out myself. It was because there're two SprayJsonSupport classes in my project:
import spray.httpx.SprayJsonSupport
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
Now obviously the latter is the correct one. Guess along the way since both Scala and Akka are evolving (fast), sometimes it becomes confusing with the namespaces and classes.
I've built a microservice using Scala and Play and now I need to create a new version of the service that returns the same data as the previous version of the service but in a different JSON format. The service currently uses implicit Writes converters to do this. My controller looks something like this, where MyJsonWrites contains the implicit definitions.
class MyController extends Controller with MyJsonWrites {
def myAction(query: String) = Action.async {
getData(query).map {
results =>
Ok(Json.toJson(results))
}
}
}
trait MyJsonWrites {
implicit val writes1: Writes[SomeDataType]
implicit val writes2: Writes[SomeOtherDataType]
...
}
Now I need a new version of myAction where the JSON is formatted differently. The first attempt I made was to make MyController a base class and have subclasses extend it with their own trait that has the implicit values. Something like this.
class MyNewContoller extends MyController with MyNewJsonWrites
This doesn't work though because the implicit values defined on MyNewJsonWrites are not available in the methods of the super class.
It would be ideal if I could just create a new action on the controller that somehow used the converters defined in MyNewJsonWrites. Sure, I could change the trait to an object and import the implicit values in each method but then I'd have to duplicate the method body of myAction so that the implicits are in scope when I call Json.toJson. I don't want to pass them as implicit parameters to a base method because there are too many of them. I guess I could pass a method as a parameter to the base method that actually does the imports and Json.toJson call. Something like this. I just thought maybe there'd be a better way.
def myBaseAction(query: String, toJson: Seq[MyResultType] => JsValue) = Action.async {
getData(query).map {
results =>
Ok(Json.toJson(results))
}
}
def myActionV1(query: String) = {
def toJson(results: Seq[MyResultType]) = {
import MyJsonWritesV2._
Json.toJson(results)
}
myBaseAction(query, toJson)
}
Instead of relying on scala implicit resolution, you can call your writes directly:
def myBaseAction(query: String, writes: Writes[MyResultType]) = Action.async {
getData(query).map { results =>
val seqWrites: Writes[Seq[MyResultType]] = Writes.seq(writes)
Ok(seqWrites.writes(results))
}
}
def myActionV1(query: String) = myBaseAction(query, MyJsonWritesV1)
def myActionV2(query: String) = myBaseAction(query, MyJsonWritesV2)
I'm finding myself in a situation in which I need to serialize into JSON a non case class.
Having a class as:
class MyClass(val name: String) {
def SaySomething() : String = {
return "Saying something... "
}
}
I've created a JsonProtocol for this class:
object MyClassJsonProtocol extends DefaultJsonProtocol {
implicit object MyClassJsonFormat extends JsonWriter[MyClass] {
override def write(obj: MyClass): JsValue =
JsObject(
"name" -> JsString(obj.name)
)
}
}
Later on in the code I import the protocol..
val aListOfMyClasses = List[MyClass]() ... // lets assume that has items and not an empty list
import spray.json._
import MyClassJsonProtocol._
val json = aListOfMyClasses.toJson
When trying to build the project I get the following error:
Cannot find JsonWriter or JsonFormat for type class List[MyClass]
spray-json has already a format for generic list and I'm providing a format for my class, what would be the problem?
Thanks in advance...!!!
When I extended MyClassJsonFormat from JsonFormat instead of JsonWriter, it stared working fine. Looks like the CollectionFormats trait will work only if you extend from JsonFormat
The following code compiles fine for me
object MyClassJsonProtocol extends DefaultJsonProtocol {
implicit object MyClassJsonFormat extends JsonFormat[MyClass] {
override def write(obj: MyClass): JsValue =
JsObject(
"name" -> JsString(obj.name)
)
override def read(json: JsValue): MyClass = new MyClass(json.convertTo[String])
}
}
The reason seems to be mentioned here:
An issue you might run into with just JsonReader/JsonWriter is that
when you try to lookup JsonReader/JsonWriter for Option or a
collection, it looks for a JsonFormat for the contained type, which
will fail. Not sure if there is something I am missing that will fix
that issue.
You and I have run into this. I don't see other way out at the moment than #user007's suggestion to use a full JsonFormat. That, itself, brings more difficulties at least to me - I was planning to use the default reader for my class.
Oh, well...
I'm trying to create a generic HTTP client in Scala using spray. Here is the class definition:
object HttpClient extends HttpClient
class HttpClient {
implicit val system = ActorSystem("api-spray-client")
import system.dispatcher
val log = Logging(system, getClass)
def httpSaveGeneric[T1:Marshaller,T2:Unmarshaller](uri: String, model: T1, username: String, password: String): Future[T2] = {
val pipeline: HttpRequest => Future[T2] = logRequest(log) ~> sendReceive ~> logResponse(log) ~> unmarshal[T2]
pipeline(Post(uri, model))
}
val genericResult = httpSaveGeneric[Space,Either[Failure,Success]](
"http://", Space("123", IdName("456", "parent"), "my name", "short_name", Updated("", 0)), "user", "password")
}
An object utils.AllJsonFormats has the following declaration. It contains all the model formats. The same class is used on the "other end" i.e. I also wrote the API and used the same formatters there with spray-can and spray-json.
object AllJsonFormats
extends DefaultJsonProtocol with SprayJsonSupport with MetaMarshallers with MetaToResponseMarshallers with NullOptions {
Of course that object has definitions for the serialization of the models.api.Space, models.api.Failure and models.api.Success.
The Space type seems fine, i.e. when I tell the generic method that it will be receiving and returning a Space, no errors. But once I bring an Either into the method call, I get the following compiler error:
could not find implicit value for evidence parameter of type
spray.httpx.unmarshalling.Unmarshaller[Either[models.api.Failure,models.api.Success]].
My expectation was that the either implicit in spray.json.DefaultJsonProtocol, i.e. in spray.json.StandardFormts, would have me covered.
The following is my HttpClient class trying it's best to be generic:
Update: Clearer/Repeatable Code Sample
object TestHttpFormats
extends DefaultJsonProtocol {
// space formats
implicit val idNameFormat = jsonFormat2(IdName)
implicit val updatedByFormat = jsonFormat2(Updated)
implicit val spaceFormat = jsonFormat17(Space)
// either formats
implicit val successFormat = jsonFormat1(Success)
implicit val failureFormat = jsonFormat2(Failure)
}
object TestHttpClient
extends SprayJsonSupport {
import TestHttpFormats._
import DefaultJsonProtocol.{eitherFormat => _, _ }
val genericResult = HttpClient.httpSaveGeneric[Space,Either[Failure,Success]](
"https://api.com/space", Space("123", IdName("456", "parent"), "my name", "short_name", Updated("", 0)), "user", "password")
}
With the above, the problem still occurs where the unmarshaller is unresolved. Help would be greatly appreciated..
Thanks.
Spray defines a default marshaller for Either[A,B] if a Marshaller[A] and Marshaller[B] are in defined scope inside the MetaMarshallers trait. But, going the other direction requires an Unmarshaller. You will need to define an in-scope Unmarshaller for Either[Failure, Success]. This cannot be coded without specific knowledge of the expected response and what the strategy will be for choosing whether to unmarshall a response as a Left or as a Right. For example, let's say you want to return a Failure on a non-200 response and a Success from a 200 json response body:
type ResultT = Either[Failure,Success]
implicit val resultUnmarshaller: FromResponseUnmarshaller[ResultT] =
new FromResponseUnmarshaller[ResultT] {
def apply(response: HttpResponse): Deserialized[ResultT] = response.status match {
case StatusCodes.Success(200) =>
Unmarshaller.unmarshal[Success](response.entity).right.map(Right(_))
case _ => Right(Left(Failure(...)))
}
}
Update
Looking deeper into this, the problem appears to be that the default eitherFormat in spray.json.StandardFormats is not a RootJsonFormat, which is required by the default JSON unmarshaller defined in spray.httpx.SprayJsonSupport. Defining the following implicit method should solve the issue:
implicit def rootEitherFormat[A : RootJsonFormat, B : RootJsonFormat] = new RootJsonFormat[Either[A, B]] {
val format = DefaultJsonProtocol.eitherFormat[A, B]
def write(either: Either[A, B]) = format.write(either)
def read(value: JsValue) = format.read(value)
}
I have an working example gist that hopefully explains how you would use this. https://gist.github.com/mikemckibben/fad4328de85a79a06bf3