I am wondering whether Play framework controller can do automatically convert between JSON and Object (case class) without some boilerplate.
As Spring MVC and Twitter's Finatra can do that. Following is the code for Finatra framework.
#Singleton
class TweetsController #Inject()(
tweetsService: TweetsService)
extends Controller {
post("/tweet") { requestTweet: TweetPostRequest =>
// requestTweet is a case class mapping json request
tweetsService.save(requestTweet)
...
}
get("/tweet/:id") { request: TweetGetRequest =>
// case class mapping json response
tweetsService.getResponseTweet(request.id)
...
}
}
However, for Play framework, we need do JSON conversation manually. Can Play support a way without using implicit to do that?
Any reasons why Play can't support that or will it support in the future release?
We use the following utility class for this purpose
/**
* Framework method for handling a request that takes a Json body as a parameter. If the JSON body can be
* parsed as a valid instance of `A` , the resulting object is passed into the body which is expected
* to produce a Result.
*
*
* Note that it is not necessary to create the Action object in the body of the supplied handler; this is
* done for you.
*
* #tparam A A case class that the input JSON should be parsed into.
* #param body The body of the handler for this request. This must be a function that will take an instance of `A`
* and use it generate a `Result`.
*
*/
def handleJsonRequest[A : Reads](body: A => Result) = Action(parse.json) { request =>
request.body.validate[A].map {body}.recoverTotal {
errors: JsError =>
throw new ...(errors)
}
}
You can use this in your handler as
def handleGet() = handleJsonRequest[Body] {body =>
...
}
Related
When I try to pass a list to the view page of a Play application I receive an error:
illegal cyclic reference involving object models
Error screenshot:
models.scala.html:
#(liValues: List[String])
#for(value <- liValues){
<li>#value</li>
}
entry in routes files:
GET /models/tictactoe controllers.ModelController.index
index method in ModelController.scala where I pass the values:
def index = Action {
Ok(views.html.models(List("Link1" , "Link2" , "Link3")))
}
Complete ModelController:
package controllers
import javax.inject._
import play.api.libs.json.Json
import play.api.mvc._
/**
* This controller creates an `Action` to handle HTTP requests to the
* application's home page.
*/
#Singleton
class ModelController #Inject()(cc: ControllerComponents) extends AbstractController(cc) {
/**
* Create an Action to render an HTML page with a welcome message.
* The configuration in the `routes` file means that this method
* will be called when the application receives a `GET` request with
* a path of `/`.
*/
def index = Action {
Ok(views.html.models(List("Link1" , "Link2" , "Link3")))
}
def sj = Action {
Ok(Json.toJson(List(1,2,3)).toString());
}
}
it seems I'm not declaring the list value in the view page correctly ?
It seems DummyPlaceHolder.scala
package models
/*
* Empty placeholder object to make sure templates keep compiling (due to
* imports in template files), even if projects don't have any models.
*/
object DummyPlaceHolder
is interfering with your views.html.models which also has models so the generated template target/scala-2.13/twirl/main/views/html/models.template.scala will have something like
import models._
object models extends ...
which causes illegal cycle. Try changing the name of your template from views.html.models to say views.html.model.
I would like to design a client that would talk to a REST API. I have implemented the bit that actually does call the HTTP methods on the server. I call this Layer, the API layer. Each operation the server exposes is encapsulated as one method in this layer. This method takes as input a ClientContext which contains all the needed information to make the HTTP method call on the server.
I'm now trying to set up the interface to this layer, let's call it ClientLayer. This interface will be the one any users of my client library should use to consume the services. When calling the interface, the user should create the ClientContext, set up the request parameters depending on the operation that he is willing to invoke. With the traditional Java approach, I would have a state on my ClientLayer object which represents the ClientContext:
For example:
public class ClientLayer {
private static final ClientContext;
...
}
I would then have some constructors that would set up my ClientContext. A sample call would look like below:
ClientLayer client = ClientLayer.getDefaultClient();
client.executeMyMethod(client.getClientContext, new MyMethodParameters(...))
Coming to Scala, any suggestions on how to have the same level of simplicity with respect to the ClientContext instantiation while avoiding having it as a state on the ClientLayer?
I would use factory pattern here:
object RestClient {
class ClientContext
class MyMethodParameters
trait Client {
def operation1(params: MyMethodParameters)
}
class MyClient(val context: ClientContext) extends Client {
def operation1(params: MyMethodParameters) = {
// do something here based on the context
}
}
object ClientFactory {
val defaultContext: ClientContext = // set it up here;
def build(context: ClientContext): Client = {
// builder logic here
// object caching can be used to avoid instantiation of duplicate objects
context match {
case _ => new MyClient(context)
}
}
def getDefaultClient = build(defaultContext)
}
def main(args: Array[String]) {
val client = ClientFactory.getDefaultClient
client.operation1(new MyMethodParameters())
}
}
I have a template structure like this:
modal.scala.view
#()
... HTML code to display a modal in my app ...
foo.scala.view
#()
#scripts {
... The scripts required by foo.scala.view components ...
... The scripts required by modal.scala.view ... I want to avoid this!
}
#main(scripts){
... HTML code of foo ...
#modal
}
main.scala.view
#(scripts: Html)
... Main HTML code ...
#scripts
I would like to keep the scripts of modal in the modal.scala.view but I cant find a way to pass the scripts from the sub-template to the parent in order to render them in the correct place of the main template. Anyideas? Thanks in advance!
I don't think there's a canonical answer to your question that the Play team has blessed, but I can think of a couple of approaches: a monadic approach and an imperative approach.
Wrap views in sub-controllers; encapsulate scripts in the output
A large project I'm working on uses this strategy. We created a MultipartHtml type that contains Html output that should be contained in the document body and a type we created called Resources which contain stuff that should go elsewhere. We treat this type like a monad, so that we can map and flatMap it to manipulate the Html document content, while accumulating and deduplicating the Resources.
All of our controllers return MultipartHtml. They construct an instance from the results of the views and then simply :+ Resource tags to the result. Our page-level controllers composite these pieces together. The core of what we do looks something like this:
/**
* Output type for components to render body, head and end-of-body content
* #param bodyMarkup the visual part of the component output
* #param resources tags for content to include in the head or footer
*/
case class MultipartHtml(bodyMarkup: Html, resources: Seq[MultipartHtml.Resource] = Nil) {
import com.huffpost.hyperion.lib.MultipartHtml._
/**
* Apply a transformation to the body content of this object
* #param bodyMapper transformation function
* #return a new object with transformed body content
*/
def map(bodyMapper: Html => Html): MultipartHtml = MultipartHtml(bodyMapper(bodyMarkup), resources)
/**
* #param bodyMapper transformation function
* #return the bodyMapper result combined with the component resource list
*/
def flatMap(bodyMapper: Html => MultipartHtml): MultipartHtml = bodyMapper(bodyMarkup) ++ resources
/**
* Add a head and/or footer content to this object
* #param resources the resources to add
* #return a new object with the resource added
*/
def ++(resources: GenTraversableOnce[Resource]): MultipartHtml = resources.foldLeft(this)(_ :+ _)
/**
* Add a head or footer content to this object
* #param resource the resource to add
* #return a new object with the resource added
*/
def :+(resource: Resource): MultipartHtml = MultipartHtml(bodyMarkup, (resources :+ resource).distinct)
/**
* Prepend a head or footer content to this object
* #param resource the resource to add
* #return a new object with the resource added
*/
def +:(resource: Resource): MultipartHtml = MultipartHtml(bodyMarkup, (resource +: resources).distinct)
/** Get tags by resource type for injection into a template */
def renderResourcesByType(resourceType: ResourceType): Html = Html(resources.filter(_.resourceType == resourceType).mkString("\n"))
}
/** Utility methods for MultipartHtml type */
object MultipartHtml {
/** Empty MultipartHtml */
def empty = MultipartHtml(Html(""))
/** A resource that can be imported in the HTML head or footer*/
trait ResourceType
trait Resource {
def resourceType: ResourceType
}
object HeadTag extends ResourceType
object FooterTag extends ResourceType
/** A style tag */
case class StyleTag(styleUrl: String) extends Resource {
val resourceType = HeadTag
override def toString = {
val assetUrl = routes.Assets.at(styleUrl).url
s"""<link rel="stylesheet" type="text/css" media="screen" href="$assetUrl">"""
}
}
/** A script tag */
case class ScriptTag(scriptUrl: String) extends Resource {
val resourceType = FooterTag
override def toString = {
val assetUrl = routes.Assets.at(s"javascript/$scriptUrl").url
s"""<script type="text/javascript" src="$assetUrl"></script>"""
}
}
}
There's a whole architecture built on top of this, but I probably shouldn't share too much, as it's an unreleased product.
Create a helper that stores scripts in a mutable collection
Another strategy might be to have your top level controller create an object that stores up the tags in a mutable data structure. I haven't tried this myself, but you could do something like:
import play.twirl.api.Html
case class TagStore(id: String) {
val tags = scala.collection.mutable.Set[Html]()
def addTag(tag: Html): Unit = {
store += tag
}
}
Then in your template, you could do:
#(addTag: Html => Unit)
#addTag {
<script src="myscript.js"></script>
}
#* Generate HTML *#
Downside here is that you have to forward this object down the line somehow, which could be a pain if your hierarchy of partial views can get deep.
I'm developing RESTful API for a web service. And I need to expose some properties that do not belong to an entity itself.
For example I have a Pizza entity object, it has it's own size and name properties. I'm outputting it in JSON format with FOSRestBundle and JMSSerializer. I've setup properties annotations for this entity to expose needed properties via serialization groups and it's working great.
But I need to add some properties that do not belong to the entity itself. For example I want my pizza to have property: isFresh that is determined by some PizzaService::isFresh(Pizza $pizza) service. How do I do this?
Should I inject some additional logic to serialization process (if so how)?
Should I create a wrapper entity with properties that I want to expose from original entity plus additional external properties?
Should I add property isFresh to the original Pizza entity and populate in in the controller before serialization?
Should I return additional data independent of entity data (in a sibling JSON properties for example)?
In other words: what are the best practices around this issue? Could you provide examples? Thank you.
I think you can do that with the VirtualProperty annotation :
/**
* #JMS\VirtualProperty
* #return boolean
*/
public function isFresh (){
...
}
Edit : another solution with the Accessor annotation
/** #Accessor(getter="getIsFresh",setter="setIsFresh") */
private $isFresh;
// ...
public function getIsFresh()
{
return $this->isFresh;
}
public function setIsFresh($isFresh)
{
$this->isFresh= $isFresh;
}
In your controller, you call the setIsFresh method
(See http://jmsyst.com/libs/serializer/master/reference/annotation)
I've decided to create my own class to serialize an entity.
Here's the example:
class PizzaSerializer implements ObjectSerializerInterface
{
/** #var PizzaService */
protected $pizzaService;
/**
* #param PizzaService $pizzaService
*/
public function __construct(PizzaService $pizzaService)
{
$this->pizzaService = $pizzaService;
}
/**
* #param Pizza $pizza
* #return array
*/
public function serialize(Pizza $pizza)
{
return [
'id' => $pizza->getId(),
'size' => $pizza->getSize(),
'name' => $pizza->getName(),
'isFresh' => $this->pizzaService->isFresh($pizza),
];
}
}
You just have to configure DC to inject PizzaService into the object serializer and then just call it like this from the controller:
$pizza = getPizzaFromSomewhere();
$pizzaSerializer = $this->get('serializer.pizza');
return $pizzaSerializer->serialize($pizza);
The object serializer will return an array that can be easily converted to JSON, XML, YAML or any other format by using real serializer like JMS Serializer. FOSRestBundle will do this automatically if you configured it so.
I have a very simple question.
In Java code, I used to use Data Transfer Objects for Requests / Responses.
For example, in my Spring webapp I was creating some request dto, like
public class SaveOfficeRequest {
private String officeName;
private String officePhone;
private String officeAddress;
/* getters / setters */
}
After that i had controller with "mapped" method like
#ResponseBody
public SaveOfficeResponse saveOffice(#RequestBody SaveOfficeRequest) { ... }
.
Every request is json request. When some controller method was called i converted request dto to domain dto entities and do some business logic.
So!
Should I save the practice in my new scala project based on Play Framework?
Case classes can be used to represent the request and response objects. This helps make the API explicit, documented and type-safe, and isolate concerns, by avoiding to use domain objects directly in external interface.
For example, for a JSON endpoint, the controller action could use a pattern like this:
request.body.asJson.map { body =>
body.asOpt[CustomerInsertRequest] match {
case Some(req) => {
try {
val toInsert = req.toCustomer() // Convert request DTO to domain object
val inserted = CustomersService.insert(toInsert)
val dto = CustomerDTO.fromCustomer(inserted)) // Convert domain object to response DTO
val response = ... // Convert DTO to a JSON response
Ok(response)
} catch {
// Handle exception and return failure response
}
}
case None => BadRequest("A CustomerInsertRequest entity was expected in the body.")
}
}.getOrElse {
UnsupportedMediaType("Expecting application/json request body.")
}