Jackson 是Java生態圈裏最流行的JSON序列化庫,它的官方網站是:https://github.com/FasterXML/jackson。html
爲何選擇 Jacksonjava
爲何選擇 Jackson 而不是更Scala範的 play-json、 circe、 json4s 等JSON序列化庫呢?這裏主要考慮是 Jackson 在Java生態圈裏更流行,相對熟悉的人更多,能夠必定程度上減輕Javaer們使用Scala時上手的難度。 同時,Jackson支持對大部分Java和Scala下的集合庫、數據類型的JSON序列化,而大部分Scala範的JSON庫只支持Scala的集合庫、case class和數據類型。當你的應用裏同時使用Java和Scala兩種不一樣的集合類型和Java style class與Scala case class時,Jackson均可以對齊完美支持。git
基於 Akka HTTP 的 marshal/unmarshal 機制,能夠很容易的集成各類序列化/反序列化工具。akka-http-json 這套庫就提供了9種不一樣的JSON序列化/反序列化方安供用戶選擇。github
咱們須要在 sbt 裏添加依賴:web
libraryDependencies += "de.heikoseeberger" %% "akka-http-jackson" % "1.22.0"
"Default ObjectMapper" should { import de.heikoseeberger.akkahttpjackson.JacksonSupport._ "從case class序列化和反序列化" in { val foo = Foo("bar", 2018) val result = Marshal(foo).to[RequestEntity].flatMap(Unmarshal(_).to[Foo]).futureValue foo mustBe result } "從數組case class序列化和反序列化" in { val foos = Seq(Foo("bar", 2018)) val result = Marshal(foos).to[RequestEntity].flatMap(Unmarshal(_).to[Seq[Foo]]).futureValue foos mustBe result } "不支持OffsetDateTime" in { val foo = FooTime("羊八井", OffsetDateTime.now()) val requestEntity = Marshal(foo).to[RequestEntity].futureValue intercept[MismatchedInputException] { throw Unmarshal(requestEntity).to[Foo].failed.futureValue } } }
能夠看到,默認的 akka-http-jackson 不支持 Java 8 新提供的時間/日期類型序列化,這是由於它默認使用的 Jackson ObjectMapper 沒有加載 JavaTimeModule 這個模塊在 https://github.com/FasterXML/jackson-modules-java8/tree/master/datetime 能夠找到JavaTimeModule這個模塊的更多詳細說明。api
/** * Automatic to and from JSON marshalling/unmarshalling usung an in-scope Jackon's ObjectMapper */ object JacksonSupport extends JacksonSupport { val defaultObjectMapper: ObjectMapper = new ObjectMapper().registerModule(DefaultScalaModule) }
首先來看看 akka-http-jackson 定義的 JacksonSupport.scala,它經過兩個隱式函數實現了 Akka HTTP 的 Marshal/Unmarshal 功能。數組
/** * HTTP entity => `A` */ implicit def unmarshaller[A]( implicit ct: TypeTag[A], objectMapper: ObjectMapper = defaultObjectMapper ): FromEntityUnmarshaller[A] = jsonStringUnmarshaller.map( data => objectMapper.readValue(data, typeReference[A]).asInstanceOf[A] ) /** * `A` => HTTP entity */ implicit def marshaller[Object]( implicit objectMapper: ObjectMapper = defaultObjectMapper ): ToEntityMarshaller[Object] = Jackson.marshaller[Object](objectMapper)
能夠看到隱式函數又分別定義了兩個和一個隱式參數,而 objectMapper: ObjectMapper = defaultObjectMaper
這個隱式參數定義了默認值,這樣在使用時咱們就能夠提供自定義的 ObjectMapper 來替代默認的 defaultObjectMapper
。先來看看怎樣使用自定義的 ObjectMapper:app
"Custom ObjectMapper" should { import de.heikoseeberger.akkahttpjackson.JacksonSupport._ implicit val objectMapper: ObjectMapper = helloscala.common.json.Jackson.defaultObjectMapper "支持OffsetDateTime" in { val foo = FooTime("羊八井", OffsetDateTime.now()) val requestEntity = Marshal(foo).to[RequestEntity].futureValue val result = Unmarshal(requestEntity).to[FooTime].futureValue foo mustBe result } "從數組case class序列化和反序列化" in { val foos = Seq(FooTime("羊八井", OffsetDateTime.now())) val results = Marshal(foos).to[RequestEntity].flatMap(Unmarshal(_).to[Seq[FooTime]]).futureValue foos mustBe results } }
經過在代碼上下文中定義一個隱式值:implicit val objectMapper: ObjectMapper = ....
(變量名能夠取任何名字,不須要是objectMapper
。可是須要保證在代碼上下文中只有一個ObjectMapper隱式類型。),Scala 編譯器在編譯代碼時將使用定義的隱式值傳入函數 unmarshaller
或 marshaller
中以替代函數定義時設置的默認值。
自定義 ObjectMapper 定義在object Jackson.scala:
implicit val defaultObjectMapper: ObjectMapper = getObjectMapper private def getObjectMapper: ObjectMapper = new ObjectMapper().findAndRegisterModules //.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")) //.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) .enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES) .enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES) .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) .enable(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS) .disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES) .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE) // 禁止反序列化時將時區轉換爲 Z .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) // 容許序列化空的對象 .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // 日期時間類型不序列化成時間戳 .disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS) // 日期時間類型不序列化成時間戳 .setSerializationInclusion(JsonInclude.Include.NON_NULL) // 序列化時不包含null的鍵
默認狀況下,JacksonSupport要求客戶端提交的HTTP請求必需設置Content-Type的mime-type類型爲:application/json
,但不少時候會遇到不那麼規範的客戶端,它們並未正確的設置HTTP請求頭。這時咱們能夠自定義JacksonSupport讓它在反序列化時支持其它Content-Type:這裏定義除了application/json
外還支持text/plain
類型的請求。
"Custom unmarshallerContentTypes" should { final object CustomJacksonSupport extends JacksonSupport { override def unmarshallerContentTypes: immutable.Seq[ContentTypeRange] = List(MediaTypes.`text/plain`, MediaTypes.`application/json`) } "text/plain unmarshal failed" in { import de.heikoseeberger.akkahttpjackson.JacksonSupport._ val entity = HttpEntity("""{"name": "羊八井", "since": 2018}""") entity.contentType.mediaType mustBe MediaTypes.`text/plain` intercept[UnsupportedContentTypeException] { throw Unmarshal(entity).to[Foo].failed.futureValue } } "text/plain unmarshal" in { import CustomJacksonSupport._ val entity = HttpEntity("""{"name": "羊八井", "since": 2018}""") entity.contentType.mediaType mustBe MediaTypes.`text/plain` val foo = Unmarshal(entity).to[Foo].futureValue foo mustBe Foo("羊八井", 2018) } }
在 Akka HTTP Routing DSL 裏使用Jackson來反序列化/序列化JSON就很是簡單了。經過entity(as[FooTime])
指令來將提交的JSON數據解析成 FooTime
樣本類(將調用 unmarshaller[A]
隱式函數),在complete
函數響應結果時將 FooTime
對象序列化成JSON字符串並設置對應的Content-Type(調用 marshaller[A]
隱式函數)。
"routing-dsl" should { import akka.http.scaladsl.server.Directives._ import de.heikoseeberger.akkahttpjackson.JacksonSupport._ implicit val objectMapper: ObjectMapper = helloscala.common.json.Jackson.defaultObjectMapper val route: Route = path("api") { post { entity(as[FooTime]) { foo => complete(foo.copy(since = foo.since.plusYears(1))) } } } "post json" in { val foo = FooTime("羊八井", OffsetDateTime.now()) Post("/api", foo) ~> route ~> check { status mustBe StatusCodes.OK contentType.mediaType mustBe MediaTypes.`application/json` val payload = responseAs[FooTime] foo.name mustBe payload.name foo.since.isBefore(payload.since) mustBe true } } }
Akka HTTP經過強大的 Marshal/Unmarshal 機制來實現數據的序列/反序列化,做爲一款工具庫 Akka HTTP 提供了足夠的靈活性,用戶能夠選擇本身喜歡的序列/反序列化工具和使用方式。對於JSON,推薦從 https://github.com/hseeberger/akka-http-json 開始,上面頗有可能找到你想要的。同時,akka-http-json也是一個不錯的學習 Akka HTTP Marshal/Unmarshal 機制的樣例。
完整的測試代碼在:data/src/test/scala/scalaweb/data/json/jackson/JacksonSupportTest.scala,能夠經過如下命令來運行它:
sbt "data/testOnly scalaweb.data.json.jackson.JacksonSupportTest"
測試結果示例:
The source code for this page can be found here.
本文節選自《Scala Web開發》一書,完整內容見:https://www.yangbajing.me/scala-web-development/data/data.1.html。