I am using Scalatra, but this question should be valid for any Scala programming. I come from a Ruby on Rails background. Simply put, using templating systems such as XML Builder or jsonbuilder (https://github.com/rails/jbuilder), I had full control on what my JSON or XML output in an RESTful API would be by creating a template such as the following:
Jbuilder.encode do |json|
json.content format_content(@message.content)
json.(@message, :created_at, :updated_at)
json.author do
json.name @message.creator.name.familiar
json.email_address @message.creator.email_address_with_name
json.url url_for(@message.creator, format: :json)
end
if current_user.admin?
json.visitors calculate_visitors(@message)
end
json.comments @message.comments, :content, :created_at
json.attachments @message.attachments do |attachment|
json.filename attachment.filename
json.url url_for(attachment)
end
end
The ideal here is, I put together a @message
object with whatever logic is required in a controller+action. That gets passed to a template that has logic such as if current_user.admin?
include some stuff, otherwise don't.
What is the equivalent tool that's available in Scala or Scalatra to do something similar? I know a serializer
will let me override the JSON or XML that gets generated from a particular model, but that's the same thing in Ruby (correct me if I'm wrong) as overriding as_json
or as_xml
. Sometimes however, templates are much more complicated and include multiple models, specific structuring of data, specific ordering of data, etc. This is the flexibility I need. Is there current a tool that allows for such templating in a Scala/Scalatra environment?
unfortunately I don't know any really good library to do this in XML (although Anti-Xml is worth taking a look). But there are such library for Json and it's the PlayJson With play json you can do basically everything you can do with Ruby's JsBuilder, except some paradigm difference:
Scala tries to be as functional as possible and many many libraries operate with unmutable data structures. Play json is not an exception. This means that you cannot just change some value deep in the json tree, you need to reconstruct the whole json object
Scala is a statically typed language. Which is great, because compiler checks all type-signatures for correctness, except we must provide those signatures.
Here's example code:
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
case class Attachment(fileName: String, url: String)
case class Author(name: String, email: String, url: String)
case class Comment(content: String, created_at: DateTime)
case class Post(author: Author, content: String, attachments: List[Attachment], comments: List[Comment], created_at: DateTime, updated_at: DateTime)
object Main {
import play.api.libs.json._
import play.api.libs.functional.syntax._
val isAdmin = true
def calculateVisits(post: Post) = 35
implicit val jodaTimeWrites: Writes[DateTime] = new Writes[DateTime] {
def writes(c: DateTime): JsValue = {
Json.toJson(c.toString(DateTimeFormat.fullDateTime()))
}
}
implicit val attachmentFormat = Json.format[Attachment]
implicit val authorWrites: Writes[Author] = (
(__ \ "name").write[String] and
(__ \ "email").write[String] and
(__ \ "url").write[String]) { unlift(Author.unapply) }
implicit val commentWrites: Writes[Comment] = (
(__ \ "content").write[String] and
(__ \ "created_at").write[DateTime]) { unlift(Comment.unapply) }
implicit val postWrites: Writes[Post] = (
(__ \ "content").write[String] and
(__ \ "created_at").write[DateTime] and
(__ \ "updated_at").write[DateTime] and
(__ \ "author").write[Author] and
(__ \ "visitors").write[Option[Int]] and
(__ \ "comments").write[List[Comment]] and
(__ \ "attachments").write[List[Attachment]]) { post: Post =>
(
post.content,
post.created_at,
post.updated_at,
post.author,
if (isAdmin) Some(calculateVisits(post)) else None,
post.comments,
post.attachments)
}
def main(args: Array[String]): Unit = {
val post = Post(
Author("David H.", "[email protected]", "http://example.com/users/1-david.json"),
"<p>This is <i>serious</i> monkey business</p>",
List(
Attachment("forecast.xls", "http://example.com/downloads/forecast.xls"),
Attachment("presentation.pdf", "http://example.com/downloads/presentation.pdf")),
List(
Comment("Hello everyone!", new DateTime()),
Comment("To you my good sir!", new DateTime())),
new DateTime(),
new DateTime())
Console println (Json prettyPrint (Json toJson post))
}
}
Notice the attachmentFormat
- it is generated by scala macros to be matched with case class definition. In my project I've never written a single manual Format override compiler generates all formats for me! But I can If I need to. Good example is the jodaTimeWrites
- default jodaTimeFormat will generate long value which is more appropriate for machine processing, but I have overridden it with my own implicit format to match ruby's sample.
The code above produces following output:
{
"content" : "<p>This is <i>serious</i> monkey business</p>",
"created_at" : "Friday, July 5, 2013 4:19:42 PM +03:00",
"updated_at" : "Friday, July 5, 2013 4:19:42 PM +03:00",
"author" : {
"name" : "David H.",
"email" : "[email protected]",
"url" : "http://example.com/users/1-david.json"
},
"visitors" : 35,
"comments" : [ {
"content" : "Hello everyone!",
"created_at" : "Friday, July 5, 2013 4:19:42 PM +03:00"
}, {
"content" : "To you my good sir!",
"created_at" : "Friday, July 5, 2013 4:19:42 PM +03:00"
} ],
"attachments" : [ {
"fileName" : "forecast.xls",
"url" : "http://example.com/downloads/forecast.xls"
}, {
"fileName" : "presentation.pdf",
"url" : "http://example.com/downloads/presentation.pdf"
} ]
}
Now instead of 21 lines of ruby code I got 33 lines of scala's somehow more complicated mappings (without case classes). Why type more? Because now I'm dead certain that when I pass Comment
instead of Attachment
I'll get compiler error, or when my colleague will change by mistake joda.time.DateFormat to java.util.DateFormat he'll receive error instead of some gibberish.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With