リクエストパラメータの型をjava.time.LocalDateTimeにした時にただの型指定ではエラーが出たので自分でdeserializer書かないといけなかった
リクエストcase classのパラメータは以下のような感じ
import com.twitter.finatra.request.FormParam
import java.time.LocalDateTime
case class CustomRequest(
@FormParam name: String,
@FormParam date: Option[LocalDateTime] // ここがデフォルトだとdeserialize出来ない
)
CustomDeserializer書かないままAPIにリクエストすると以下のようなレスポンスが返ってきた
リクエストパラメータはdateが2017-01-20 00:00:00
{
"errors": {
"date": "Can not construct instance of java.time.LocalDateTime: no String-argument constructor/factory method to deserialize from String value ('2017-01-20 00:00:00')\n at [Source: N/A; line: -1, column: -1]"
}
}
参照
今回は先に書いておく
日本語情報無くてつらまってきた
Finatra結構使ってるよという方 とか絵文字でもいいのでコメント下さい…
Finatra公式 Jackson Integration Adding a Custom Serializer or Deserializer
https://twitter.github.io/finatra/user-guide/json/index.html#customization
finatra/jackson/src/main/scala/com/twitter/finatra/json/internal/serde/FinatraSerDeSimpleModule.scala
https://github.com/twitter/finatra/blob/d0c659e3d139b7c3c1c075133c65a9828a00cbb1/jackson/src/main/scala/com/twitter/finatra/json/internal/serde/FinatraSerDeSimpleModule.scala
finatra/jackson/src/test/scala/com/twitter/finatra/json/tests/internal/ExampleCaseClasses.scala
https://github.com/twitter/finatra/blob/master/jackson/src/test/scala/com/twitter/finatra/json/tests/internal/ExampleCaseClasses.scala
Custom JSON Deserialization with Jackson
https://dzone.com/articles/custom-json-deserialization-with-jackson
Getting Started with Custom Deserialization in Jackson
http://www.baeldung.com/jackson-deserialization
解決手順とコード
解決手順は公式に書いてある、だが例がもうちょっと…
なので動く例も載せる
解決手順
- 指定の型のdeserializer書く
- そのdeserializerをJacksonModulesに追加作成
- FinatraのHttpServerのjacksonModuleを作成したJacksonModulesでoverride
以上
以下はCustomJacksonModuleを作成するコード
package yourpackage.modules
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import com.fasterxml.jackson.annotation.JsonInclude.Include
import com.fasterxml.jackson.core.JsonGenerator.Feature
import com.fasterxml.jackson.core.{JsonParser, ObjectCodec}
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.{DeserializationContext, JsonNode, ObjectMapper, PropertyNamingStrategy}
import com.fasterxml.jackson.databind.module.SimpleModule
import com.twitter.finatra.json.modules.FinatraJacksonModule
// custom deserializer
class CustomLocalDateTimeDeserializer extends JsonDeserializer[LocalDateTime] { // LocalDateTimeが変換したい型
override def deserialize(jp: JsonParser, ctxt: DeserializationContext) = {
import scala.util.control.Exception._
val oc: ObjectCodec = jp.getCodec()
val node: JsonNode = oc.readTree(jp)
val strDate: String = node.asText()
val parseRes = catching(classOf[java.time.format.DateTimeParseException]) opt LocalDateTime.parse(strDate, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
parseRes match {
case Some(localDateTime) => localDateTime
case None => null
}
}
}
// Jackson SimpleModule for custom deserializer
class CustomDeserializerModule extends SimpleModule {
addDeserializer(classOf[LocalDateTime], new CustomLocalDateTimeDeserializer)
}
// custom FinatraJacksonModule which replace the framework module
object CustomJacksonModule extends FinatraJacksonModule {
override val additionalJacksonModules = Seq(
new CustomDeserializerModule
)
override val serializationInclusion = Include.NON_EMPTY
override val propertyNamingStrategy = PropertyNamingStrategy.SNAKE_CASE // リクエストパラメータがスネークケース dateOfBirthではなくdate_of_birthじゃないとdeserializeしようとしない
override def additionalMapperConfiguration(mapper: ObjectMapper) {
mapper.configure(Feature.WRITE_NUMBERS_AS_STRINGS, true)
}
}
以下は作成したCustomJacksonModuleでjacksonModuleをoverrideするコード
class MainServer extends HttpServer with Logging {
override def jacksonModule = CustomJacksonModule // ここ書かないと適用されない
override def configureHttp(router: HttpRouter): Unit = {
router
.filter[CommonFilters]
.add[SomeController]
}
}
上記を適用すると2017-01-20 00:00:00
のようなパラメータをLocalDateTime型で受け取る事ができるようになった
2017-01-20
だけだとparseに失敗してdateがOption型なのでNoneになる
以上