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.", "david@heinemeierhansson.com", "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" : "david@heinemeierhansson.com",
"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.