5

我正在使用 Scalatra,但这个问题应该对任何 Scala 编程都有效。我来自 Ruby on Rails 背景。简而言之,使用 XML Builder 或 jsonbuilder ( https://github.com/rails/jbuilder ) 等模板系统,我可以通过创建模板来完全控制 RESTful API 中的 JSON 或 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

这里的理想是,我将一个@message对象与控制器+动作中所需的任何逻辑放在一起。这被传递给具有逻辑的模板,例如if current_user.admin?包含一些东西,否则不要。

Scala 或 Scalatra 中可用于执行类似操作的等效工具是什么?我知道 aserializer会让我覆盖从特定模型生成的 JSON 或 XML,但这与 Ruby 中的相同(如果我错了,请纠正我)作为覆盖as_jsonas_xml. 然而,有时模板要复杂得多,包括多个模型、特定的数据结构、特定的数据排序等。这就是我需要的灵活性。当前是否有允许在 Scala/Scalatra 环境中进行此类模板化的工具?

4

2 回答 2

4

不幸的是,我不知道任何真正好的库可以在 XML 中执行此操作(尽管Anti-Xml值得一看)。但是有这样的 Json 库,它是PlayJson 使用 play json 你基本上可以用 Ruby 的 JsBuilder 做所有你可以做的事情,除了一些范式差异:

  1. Scala 试图尽可能地发挥功能,并且许多库都使用不可变的数据结构进行操作。玩json也不例外。这意味着您不能只更改json 树深处的某些值,您需要重建整个 json 对象

  2. Scala 是一种静态类型语言。这很好,因为编译器检查所​​有类型签名的正确性,除了我们必须提供这些签名。

这是示例代码:

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

}

请注意attachmentFormat- 它是由 scala 宏生成的,以与案例类定义匹配。在我的项目中,我从未编写过一个手动格式覆盖编译器为我生成所有格式!但如果需要,我可以。很好的例子是jodaTimeWrites- 默认 jodaTimeFormat 将生成更适合机器处理的 long 值,但我已经用我自己的隐式格式覆盖它以匹配 ruby​​ 的示例。

上面的代码产生以下输出:

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

现在,我得到了 33 行 scala 的某种更复杂的映射(没有案例类),而不是 21 行 ruby​​ 代码。为什么要多打字?因为现在我很确定当我通过Comment而不是Attachment我会得到编译器错误,或者当我的同事错误地将 joda.time.DateFormat 更改为 java.util.DateFormat 他会收到错误而不是一些乱码。

于 2013-07-05T13:31:12.397 回答
0

这是使用 Scala 的内置 XML 文字和出色的argonaut.io JSON 序列化库的答案草图:

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

}

这将为您提供:

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>
于 2013-07-05T13:48:24.347 回答