Spray参数转化

最近用spray做点东西,刚开始入门,把doc大概过了一遍,最灵活的要数它的routing-DSL了。当然由于scala强大的特性,很多不同于Java的东西,源码看起来还是相当费劲的,尤其是对于我这种scala使用没多久的新手。

今天碰到的问题是如何把参数转化成想要的类型,比如?age=25&birth=1988-01-23,能接收为Int和Date参数。很快在文档里找到了方法,如下写法:


case class Color(red: Int, green: Int, blue: Int)

val route =
  path("color") {
    parameters('red.as[Int], 'green.as[Int], 'blue.as[Int]) { (red, green, blue) =>
      val color = Color(red, green, blue)
      doSomethingWith(color) // route working with the Color instance
    }
  }

但是我在自己写的时候,发现这样写Date会导致编译错误,


val route: Route =
    path("stat" / Rest) { path =>
      parameters('age.as[Int], 'birth.as[java.util.Date]) { (age, birth) =>
        get {
          complete {
            path + age.toString + birth
          }
        }
      }
    }



[info] Compiling 1 Scala source to D:\study\spray-template\target\scala-2.11\classes...
[error] D:\study\spray-template\src\main\scala\com\example\MyService.scala:60: too many arguments for method parameters: (pdm: spr
ay.routing.directives.ParamDefMagnet)pdm.Out
[error]       parameters('age.as[Int], 'birth.as[java.util.Date]) { (age, birth) =>
[error]                 ^



错误完全不知道在说什么,经典的方法,把错误信息google一下也没有找到满意的答案。然后我开始自己思考,spray或者scala没理由强大到能你随便写一个类型A,as[A]就能把string转化好的啊,一定有什么magic的事情在背后默默地发生了。


于是首先ctrl+b(idea的快捷键,用了idea后我再也不想换回eclipse了,这不是广告)点进去之后发现是一个case class NameReceptacle,不是我想象的直接返回类型A。继续看parameters,点进去之后是:


def parameters(pdm: ParamDefMagnet): pdm.Out = pdm()



然后我就傻了,然后就是一堆implicit的东西,完全不知道在讲什么!然后去 doc里找,看到了 The Magnet Pattern  然后似乎看到了希望,点开,好长一篇E文啊,心生胆怯!不过后来还是坚持看完了,简单来说就是scala下一种为了解决传统的method overloading由于类型擦除等造成的一些不足的设计模式吧。然后由于'age.as[Int]返回的是NameReceptacle[Int]类型,就找到了“重载”的方法

implicit def forNR[T](implicit fsod: FSOD[T]) = extractParameter[NameReceptacle[T], T] { nr ⇒
    filter(nr.name, fsod)
  }
可以看到它需要一个implicit的FSOD?是这个东西

import spray.httpx.unmarshalling.{ FromStringOptionDeserializer ⇒ FSOD, _ }
type FromStringOptionDeserializer[T] = Deserializer[Option[String], T]

事情比较清楚了,spray确实没理由强大到随便给个类型A它都能把string转化为A。原来是需要一个Deserializer来做这件事情,因为是implicit,所以你不需要显示写出来。看样子'age.as[Int]应该是spray默认有定义一个Deserializer[Option[String], Int]即把String转化为Int的implicit函数。那Date不行报错,应该就是没有Deserializer[Option[String], Date],那自己定义看看吧。

implicit def string2Date = new Deserializer[Option[String], Date] {
    override def apply(v1: Option[String]): Deserialized[Date] = v1 match {
      case None => Left(ContentExpected)
      case Some(str) => {
        val sdf = new SimpleDateFormat("yyyy-MM-dd")
        try {
          val date = sdf.parse(str)
          Right(date)
        } catch {
          case _: Throwable => Left(MalformedContent("format yyyy-MM-dd"))
        }
      }
    }
  }



再编译,没问题,运行也ok!

后来发现object Deserializer里有一个方法,可以把普通函数f:A=> B“提升”为Deserializer

implicit def fromFunction2Converter[A, B](implicit f: A ⇒ B) =
    new Deserializer[A, B] {
      def apply(a: A) = {
        try Right(f(a))
        catch {
          case NonFatal(ex) ⇒ Left(MalformedContent(ex.toString, ex))
        }
      }
    }



于是可以简化为

implicit def str2Date(str: String) = {
    val sdf = new SimpleDateFormat("yyyy-MM-dd")
      sdf.parse(str)
  }



看上去清爽许多了!


最后说一个小技巧,一开始没明白'age.as[Int]是什么类型,一种方法是ctrl+b点进去看看,还有一种,你可以定义一个

val x = 'age.as[Int]



然后把鼠标移动到x,alt+enter,选择"add type annotation to value definition"就变成

val x: NameReceptacle[Int] = 'age.as[Int]



这在某些情况下还是很方便的。


scala确实很强大灵活,学习曲线有高,继续努力。。。。。。。。




你可能感兴趣的:(scala,DSL,Routing,Implicit,spray)