Akka(34): Http:Unmarshalling,from Json
Unmarshalling是Akka-http內把網上可傳輸格式的數據轉變成程序高級結構話數據的過程,比如把Json數據轉換成某個自定義類型的實例。按具體流程來說就是先把Json轉換成可傳輸格式數據如:MessageEntity,HttpRequest,HttpReponse等,然後再轉換成程序高級結構數據如classXX實例。Unmarshalling對一個A類實例到B類實例的轉換是通過Unmarshaller[A,B]來實現的:
trait Unmarshaller[-A, B] extends akka.http.javadsl.unmarshalling.Unmarshaller[A, B] {...}
object Unmarshaller
extends GenericUnmarshallers
with PredefinedFromEntityUnmarshallers
with PredefinedFromStringUnmarshallers {
// format: OFF
//#unmarshaller-creation
/**
* Creates an `Unmarshaller` from the given function.
*/
def apply[A, B](f: ExecutionContext ? A ? Future[B]): Unmarshaller[A, B] =
withMaterializer(ec => _ => f(ec))
...}
從Unmarshaller的構建函數apply可以估計它的作用應該與函數A=>Future[B]很相似。A代表網上可傳輸類型如MessageEntity、HttpRequest,B代表某種程序高級數據類型。因為A到B的轉換是non-blocking的,所以可以立即返回Future類型結果。Akka-http按被轉換對象類型分類命名了下面這些類型別名:
type FromEntityUnmarshaller[T] = Unmarshaller[HttpEntity, T]
type FromMessageUnmarshaller[T] = Unmarshaller[HttpMessage, T]
type FromResponseUnmarshaller[T] = Unmarshaller[HttpResponse, T]
type FromRequestUnmarshaller[T] = Unmarshaller[HttpRequest, T]
type FromByteStringUnmarshaller[T] = Unmarshaller[ByteString, T]
type FromStringUnmarshaller[T] = Unmarshaller[String, T]
type FromStrictFormFieldUnmarshaller[T] = Unmarshaller[StrictForm.Field, T]
Akka-http對以下類型提供了自動的Unmarshalling轉換:
PredefinedFromStringUnmarshallers
Byte
Short
Int
Long
Float
Double
Boolean
PredefinedFromEntityUnmarshallers
Array[Byte]
ByteString
Array[Char]
String
akka.http.scaladsl.model.FormData
GenericUnmarshallers
Unmarshaller[T, T] (identity unmarshaller)
Unmarshaller[Option[A], B], if an Unmarshaller[A, B] is available
Unmarshaller[A, Option[B]], if an Unmarshaller[A, B] is available
也就是說Akka-http提供了這些U類型的Unmarshaller[U,B]隱式實例。Akka-http也提供了工具類型Unmarshal:
object Unmarshal {
def apply[T](value: T): Unmarshal[T] = new Unmarshal(value)
}
class Unmarshal[A](val value: A) {
/**
* Unmarshals the value to the given Type using the in-scope Unmarshaller.
*
* Uses the default materializer [[ExecutionContext]] if no implicit execution context is provided.
* If you expect the marshalling to be heavy, it is suggested to provide a specialized context for those operations.
*/
def to[B](implicit um: Unmarshaller[A, B], ec: ExecutionContext = null, mat: Materializer): Future[B] = {
val context: ExecutionContext = if (ec == null) mat.executionContext else ec
um(value)(context, mat)
}
}
我們可以通過Unmarshal.to[B]把Unmarshal[A]轉換成Future[B]。註意:這一步只包括了從網上可傳輸類型到程序類型轉換這一過程,不包括具體實現時的Json轉換。下面是一些Unmarshal的用例:
import akka.actor._
import akka.stream._
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._
object Unmarshalling {
implicit val httpSys = ActorSystem("httpSystem")
implicit val httpMat = ActorMaterializer()
implicit val httpEC = httpSys.dispatcher
val futInt = Unmarshal(43).to[Int]
val futBoolean = Unmarshal("0").to[Boolean]
val futString = Unmarshal(HttpEntity("Hello")).to[String]
val futHello = Unmarshal(HttpRequest(method = HttpMethods.GET, entity = HttpEntity("hello")))
}
以上都是已知類型之間轉換,可能沒什麽實際用途,不像marshalling:中間層Marshalling有實際轉換的需要。Unmarshalling可以直接進行Json到自定義類型之間的轉換,如:
val route = (path("User") & post) { entity(as[User]){ user =>
complete(Future(s"inserting user: $user"))
}} ~
(path("Item"/IntNumber) & put) { id => entity(as[Item]){ item =>
complete(Future(s"update item $id: $item"))
}}
以上是通過Directive as[???]實現的:
/**
* Returns the in-scope [[FromRequestUnmarshaller]] for the given type.
*
* @group marshalling
*/
def as[T](implicit um: FromRequestUnmarshaller[T]) = um
這需要把FromRequestUmarshaller[T]放在可視域內,FromRequestUmarshaller[T]實際是Unmarshaller[T,B]的別名:
type FromRequestUnmarshaller[T] = Unmarshaller[HttpRequest, T]
在上篇討論我們介紹了Akka-http的Marshalling是type-class模式的。其中關鍵可以參考上篇討論。現在我們需要這些Unmarshaller的隱式實例:
trait Formats extends SprayJsonSupport with DefaultJsonProtocol object Converters extends Formats { case class User(id: Int, name: String) case class Item(id: Int, name: String, price: Double) implicit val itemFormat = jsonFormat3(Item.apply) implicit val userFormat = jsonFormat2(User.apply) } object Unmarshalling { import Converters._ ...
如果使用Json4s的實現方式,我們需要如下提供這些隱式實例:
trait JsonCodec extends Json4sSupport {
import org.json4s.DefaultFormats
import org.json4s.ext.JodaTimeSerializers
implicit val serilizer = jackson.Serialization
implicit val formats = DefaultFormats ++ JodaTimeSerializers.all
}
object JsConverters extends JsonCodec
Json4s的具體用例如下:
import scala.collection.mutable._
case class User(id: Int, name: String)
class Item(id: Int, name: String, price: Double)
object AnyPic {
val area = 10
val title = "a picture"
val data = ArrayBuffer[Byte](1,2,3)
}
val route = (path("User") & post) { entity(as[User]){ user =>
complete(Future(s"inserting user: $user"))
}} ~
(path("Item"/IntNumber) & put) { id => entity(as[Item]){ item =>
complete(Future(s"update item $id: $item"))
}} ~
(path("Picture") & put) { entity(as[AnyPic.type]){ pic =>
complete(Future(s"insert picture: $pic"))
}}
從功能上和表達靈活性來講,Json4s的實現方式要占優。
下面就是本次討論的示範源代碼:
Unmarshalling
import akka.actor._
import akka.stream._
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._
import scala.concurrent._
import akka.http.scaladsl.marshallers.sprayjson._
import spray.json._
trait Formats extends SprayJsonSupport with DefaultJsonProtocol
object Converters extends Formats {
case class User(id: Int, name: String)
case class Item(id: Int, name: String, price: Double)
implicit val itemFormat = jsonFormat3(Item.apply)
implicit val userFormat = jsonFormat2(User.apply)
}
object Unmarshalling {
import Converters._
implicit val httpSys = ActorSystem("httpSystem")
implicit val httpMat = ActorMaterializer()
implicit val httpEC = httpSys.dispatcher
val futInt = Unmarshal(43).to[Int]
val futBoolean = Unmarshal("0").to[Boolean]
val futString = Unmarshal(HttpEntity("Hello")).to[String]
val futHello = Unmarshal(HttpRequest(method = HttpMethods.GET, entity = HttpEntity("hello")))
val route = (path("User") & post) { entity(as[User]){ user =>
complete(Future(s"inserting user: $user"))
}} ~
(path("Item"/IntNumber) & put) { id => entity(as[Item]){ item =>
complete(Future(s"update item $id: $item"))
}}
}
Json4sUnmarshalling
import akka.actor._
import akka.stream._
import akka.http.scaladsl.server.Directives._
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import org.json4s.jackson
import scala.concurrent._
trait JsonCodec extends Json4sSupport {
import org.json4s.DefaultFormats
import org.json4s.ext.JodaTimeSerializers
implicit val serilizer = jackson.Serialization
implicit val formats = DefaultFormats ++ JodaTimeSerializers.all
}
object JsConverters extends JsonCodec
object Json4sUnmarshalling {
import JsConverters._
implicit val httpSys = ActorSystem("httpSystem")
implicit val httpMat = ActorMaterializer()
implicit val httpEC = httpSys.dispatcher
import scala.collection.mutable._
case class User(id: Int, name: String)
class Item(id: Int, name: String, price: Double)
object AnyPic {
val area = 10
val title = "a picture"
val data = ArrayBuffer[Byte](1,2,3)
}
val route = (path("User") & post) { entity(as[User]){ user =>
complete(Future(s"inserting user: $user"))
}} ~
(path("Item"/IntNumber) & put) { id => entity(as[Item]){ item =>
complete(Future(s"update item $id: $item"))
}} ~
(path("Picture") & put) { entity(as[AnyPic.type]){ pic =>
complete(Future(s"insert picture: $pic"))
}}
}
Akka(34): Http:Unmarshalling,from Json