2013-06-27 12 views
5

Ich benutze Scalatra, aber diese Frage sollte für jede Scala Programmierung gültig sein. Ich komme von einem Ruby on Rails Hintergrund. Einfach ausgedrückt, Templating-Systeme wie XML Builder oder jsonbuilder (https://github.com/rails/jbuilder) verwendet wird, hatte ich die volle Kontrolle darüber, was meine JSON oder XML-Ausgabe in einer RESTful API, indem Sie eine Vorlage wie die folgenden sein würde:Scala mit JSON und/oder XML Vorlagen

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 

Die ideal hier ist, stelle ich ein @message Objekt mit welcher Logik in einem Controller + Aktion erforderlich ist. Das wird an eine Vorlage weitergegeben, die Logik wie if current_user.admin? enthält einige Sachen, sonst nicht.

Was ist das entsprechende Werkzeug, das in Scala oder Scalatra verfügbar ist, um etwas Ähnliches zu tun? Ich weiß, ein serializer wird mich JSON oder XML überschreiben, die von einem bestimmten Modell generiert wird, aber das ist die gleiche Sache in Ruby (korrigieren Sie mich, wenn ich falsch liege) als überschreiben as_json oder as_xml. Manchmal sind Vorlagen jedoch viel komplizierter und umfassen mehrere Modelle, spezifische Strukturierung von Daten, spezifische Reihenfolge der Daten usw. Dies ist die Flexibilität, die ich brauche. Gibt es aktuell ein Werkzeug, das solche Vorlagen in einer Scala/Scalatra-Umgebung erlaubt?

Antwort

4

Leider kenne ich keine wirklich gute Bibliothek, um dies in XML zu tun (obwohl Anti-Xml einen Blick wert ist). Aber es gibt solche Bibliothek für Json und es ist das PlayJson Mit Spiel json können Sie im Grunde alles tun Sie mit Rubys JSBuilder tun können, mit Ausnahme einiger Paradigma Unterschied:

  1. Scala versucht, so funktional wie möglich zu sein, und viele viele Bibliotheken arbeiten mit unveränderbaren Datenstrukturen. Play Json ist keine Ausnahme. Dies bedeutet, dass Sie nicht einfach einen Wert tief in der JSON-Struktur ändern können, müssen Sie das gesamte JSON-Objekt zu rekonstruieren

  2. Scala ist eine statisch typisierte Sprache. Das ist großartig, weil der Compiler alle Typ-Signaturen auf Korrektheit überprüft, außer dass wir diese Signaturen bereitstellen müssen.

Hier ist Beispielcode:

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)) 
    } 

} 

Beachten Sie die attachmentFormat - durch scala Makros erzeugt wird mit Fallklassendefinition angepasst werden. In meinem Projekt habe ich noch nie ein einziges Handbuch geschrieben. Format Override Compiler erzeugt alle Formate für mich! Aber ich kann, wenn ich muss. Ein gutes Beispiel ist die jodaTimeWrites - default jodaTimeFormat erzeugt einen langen Wert, der für die Maschinenverarbeitung besser geeignet ist, aber ich habe ihn mit meinem eigenen impliziten Format überschrieben, um Rubys Sample zu vergleichen.

Der obige Code erzeugt folgende Ausgabe:

{ 
    "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" 
    } ] 
} 

nun statt 21 Zeilen von Ruby-Code habe ich 33 Zeilen von Scalas irgendwie komplizierten Mappings (ohne Fallklassen). Warum mehr tippen? Denn jetzt bin ich todsicher, dass wenn ich Comment statt Attachment übergebe, bekomme ich einen Compilerfehler, oder wenn mein Kollege irrtümlicherweise joda.time.DateFormat zu java.util.DateFormat ändert, wird er statt eines Kauderwelschs Fehler bekommen .

0

Hier ist eine Skizze einer Antwort mit Scala integrierten XML-Literale und die ausgezeichnete argonaut.io JSON Serialisierung Bibliothek:

// dependencies for use with scala 2.10.2: 
// "io.argonaut" %% "argonaut" % "6.0-RC3" 
// "org.scalaz" %% "scalaz-core" % "7.0.1" 

import scalaz._, Scalaz._ 
import argonaut._, Argonaut._ 

object ArgonautJsonExample extends App { 

    case class File(name: String, url: String = "http://my.dummy.url") 
    case class Message(name: String, isAdmin: Boolean, attachmentOpt: Option[File]) 
    val message = 
    new Message(
     "Erik", 
     true, 
     Some(File("strawberry.png")) 
    ) 

    val json: Json = 
    ("name" := message.name) ->: 
    ("filename" :=? message.attachmentOpt.map(_.name)) ->?: 
    ("url" :=? message.attachmentOpt.map(_.url)) ->?: 
    jEmptyObject 


    val myXml = 
    <myObject> 
     <author> 
     <name>{message.name}</name> 
     </author> 
     { 
     message.attachmentOpt map { file => 
      <attachment url={file.url}> 
      {file.name} 
      </attachment> 
     } getOrElse xml.NodeSeq.Empty 
     } 
    </myObject> 


    println("json = " + json) 
    println("") 
    println("xml = " + myXml) 

} 

Dies wird Ihnen geben:

json = {"url":"http://my.dummy.url","filename":"strawberry.png","name":"Erik"} 

xml = <myObject> 
    <author> 
    <name>Erik</name> 
    </author> 
    <attachment url="http://my.dummy.url"> 
     strawberry.png 
     </attachment> 
</myObject>