2013-06-27 10 views
5

Używam Scalatra, ale to pytanie powinno być ważne dla dowolnego programowania Scala. Pochodzę z tła Ruby on Rails. Po prostu, przy użyciu systemów szablonów, takich jak XML Builder lub jsonbuilder (https://github.com/rails/jbuilder), miałem pełną kontrolę nad co byłoby moje JSON lub wyjście XML w relaksującego API poprzez tworzenie szablonu, takich jak:Scala przy użyciu szablonów JSON i/lub XML

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 Ideałem jest, że skompletowałem obiekt o dowolnej logice w kontrolerze + akcji. To przechodzi do szablonu, który ma logikę, taką jak if current_user.admin?, zawiera pewne rzeczy, w przeciwnym razie nie.

Jakie jest równoważne narzędzie dostępne w Scali lub Scalatrze, aby zrobić coś podobnego? Wiem, że serializer pozwoli mi przesłonić JSON lub XML, który zostanie wygenerowany z konkretnego modelu, ale to samo w Ruby (popraw mnie, jeśli się mylę) jako nadrzędny as_json lub as_xml. Czasami jednak szablony są znacznie bardziej skomplikowane i obejmują wiele modeli, konkretne struktury danych, specyficzne porządkowanie danych itp. Jest to elastyczność, której potrzebuję. Czy istnieje obecnie narzędzie, które pozwala na takie szablony w środowisku Scala/Scalatra?

Odpowiedz

4

Niestety nie znam żadnej naprawdę dobrej biblioteki, która zrobi to w XML (chociaż warto przyjrzeć się Anti-Xml). Ale są takie biblioteki dla Json i jest to PlayJson z luzem json można zrobić w zasadzie wszystko, co można zrobić z Ruby JsBuilder, z wyjątkiem jakiejś różnicy paradygmatu:

  1. Scala stara się być tak funkcjonalne, jak to możliwe i wiele wiele biblioteki działają z niepoprawnymi strukturami danych. Zagraj w json nie jest wyjątkiem. Oznacza to, że nie można zmienić wartości w głębi drzewa json, należy zrekonstruować cały obiekt json. Jest to język statycznie napisany. Co jest świetne, ponieważ kompilator sprawdza poprawność wszystkich podpisów typów, z tym że musimy dostarczyć te podpisy.

Oto przykładowy kod:

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

} 

Zawiadomienie attachmentFormat - to są generowane przez makr Scala, należy dostosować do definicji klasy przypadek. W moim projekcie nigdy nie napisałem ani jednego podręcznika do kompilatora formatu, który generuje dla mnie wszystkie formaty! Ale mogę, jeśli muszę. Dobrym przykładem jest jodaTimeWrites - domyślnie jodaTimeFormat wygeneruje długą wartość, która jest bardziej odpowiednia do przetwarzania maszynowego, ale nadpisałem ją moim własnym niejawnym formatem, aby dopasować próbkę ruby.

Powyższy kod tworzy następujące wyjścia:

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

Teraz zamiast 21 linii kodu ruby ​​Mam 33 wierszy Scala jakoś bardziej skomplikowanych odwzorowań (bez klas przypadku). Po co pisać więcej? Ponieważ teraz nie jestem pewien, że gdy minie Comment zamiast Attachment, otrzymam błąd kompilatora, lub gdy mój kolega zmieni się przez pomyłkę joda.time.DateFormat na java.util.DateFormat, otrzyma błąd zamiast bełkotu .

0

Oto szkic odpowiedzi przy użyciu Scala wbudowanego w literały XML i doskonałej biblioteki argonaut.io JSON serializacji:

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

} 

Będzie to dadzą Ci:

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> 
Powiązane problemy