Akka(34): Http:Unmarshalling,from Json

Keywords: Scala JSON

Unmarshalling is a process in Akka-http to convert data in a transportable format on the Internet into program high-level structured speech data, such as converting Json data into an instance of a custom type. According to the specific process, Json is first converted into transferable format data such as Message Entity, HttpRequest, HttpReponse, etc., and then into program high-level structure data such as classXX instance. Unmarshalling converts a class A instance to a class B instance through 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))
...}

From Unmarshaller's construction function apply, it can be estimated that its function should be very similar to function A=> Future [B]. A stands for transportable types such as Message Entity, HttpRequest, and B for advanced data types of a program. Because the conversion from A to B is non-blocking, you can return Future type results immediately. Akka-http named the following types of aliases according to the type of converted object:

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 provides automatic Unmarshalling transformations for the following types:

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

That is to say, Akka-http provides implicit instances of these U-type Unmarshaller[U,B]. Akka-http also provides the tool type 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)
  }
}

We can convert Unmarshal[A] into Future[B] through Unmarshal.to[B]. Note: This step only covers the process of converting from a transferable type to a program type on the Internet, excluding the Json conversion at the time of implementation. Here are some use cases of 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")))

}

All of these are known type-to-type conversions, which may be of little practical use, unlike marshalling: the mid-tier Marshalling has the need for practical conversions. Unmarshalling can directly convert Json to custom types, such as:

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

The above is achieved through Directive as [???]:

 /**
   * Returns the in-scope [[FromRequestUnmarshaller]] for the given type.
   *
   * @group marshalling
   */
  def as[T](implicit um: FromRequestUnmarshaller[T]) = um

This requires that FromRequest Umarshaller [T] be placed in the visual domain, and FromRequest Umarshaller [T] is actually an alias for Unmarshaller[T,B]:

type FromRequestUnmarshaller[T] = Unmarshaller[HttpRequest, T]

In the previous discussion, we introduced that Marshalling of Akka-http is a type-class model. The key points can be referred to the previous discussion. Now we need these implicit examples of 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._
...

If we use the implementation of Json4s, we need to provide these implicit examples as follows:

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

The specific uses of Json4s are as follows:

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

In terms of function and expressive flexibility, Json4s has the advantage of implementation.

The following is the demo source code for this discussion:

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

Posted by bb_sarkar on Sun, 06 Jan 2019 19:03:09 -0800