1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Json4sのCustomSerializerを使ってJsonの日付をZonedDateTimeにしたい

Last updated at Posted at 2019-01-14

目的

java8から導入された各種日付型が便利です。

不変でスレッドセーフ、かつDateTimeFormatterと組み合わせて日付文字列をうまい具合に変換も可能です。

今回Json4sでJsonを扱っていますが、jsonの日付文字列もjava8の日付型に変換して扱いたいです。
公式をみると、java.util.Dateへの変換は容易にできそうですが、それ以外は自分でCustomSerializerを定義する必要がありそうです。

参考: json4s/json4s: A single AST to be used by other scala json libraries

2019/01/15 追記

json4s 3.6以降の場合はCustomSerializerを使用せず日付変換できますので、だいぶ簡単になりました。例はこちら

今回は例として、Jsonの文字列をZonedDateTimeを含むCase Classへ変換します。

例として、記事コンテンツを表すPostクラスを定義してみます。

import java.time.ZonedDateTime

case class Post(title: String, content: String, dateTime: ZonedDateTime)

新しい記事のjsonが投稿されたとします。これをPostへ変換します。変換には、CustomSerializerを継承したPostSerializerを定義すればOkです。

{
  "title": "Json4sの使い方",
  "content": "Json4sの使い方は...このようになってます",
  "datetime": "2019-01-12T03:15:00Z"
  }

CustomSerializerの定義をまず見てみます。

class CustomSerializer[A: Manifest](
  ser: Formats => (PartialFunction[JValue, A], PartialFunction[Any, JValue])) extends Serializer[A]

定義だけだと分かりづらいですが、serはFormatを引数にとり、(deserialize関数, serialize関数)という形式を返す関数です。

PostSerializerを定義してみます。複雑な書き方で申し訳ないですが、引数の型が特殊なのでいい書き方が思いつかない。。

import java.time.format.DateTimeFormatter
import java.time.{Instant, ZoneId, ZonedDateTime}

import org.json4s.CustomSerializer
import org.json4s.JsonAST.JObject
import org.json4s.JsonDSL._

class PostSerializer extends CustomSerializer[Post](format => (
// deserialze関数
{
  case jObject: JObject =>
    implicit val fmt = format

    val title = (jObject \ "title").extract[String]
    val content = (jObject \ "content").extract[String]
    val datetimeStr = (jObject \ "datetime").extract[String]
    val datetime = PostSerializer.parseToZonedDateTime(datetimeStr)

    Post(title, content, datetime)
},
// serialze関数
{
  case rssItem: Post =>
    ("title" -> rssItem.title) ~
      ("content" -> rssItem.content) ~
      ("datetime" -> PostSerializer.strFromZonedDateTime(rssItem.dateTime))
}
))

object PostSerializer {
  private def strFromZonedDateTime(t: ZonedDateTime): String =
    t.format(DateTimeFormatter.ISO_INSTANT)

  private def parseToZonedDateTime(t: String): ZonedDateTime = {
    Instant.parse(t).atZone(ZoneId.systemDefault())
  }
}

serialize関数内では、~というJValueのマージ関数を使っています。一見わかりにくいです。使用するにはimport org.json4s.JsonDSL._を宣言します。

PostSerializerはコンパニオンオブジェクトも定義しています。メソッドでZonedDateTimeとStringの相互変換を行います。
ZonedDateTimeは便利とはいえ、stringから変換するにはInstantなどを処理の間に挟まないといけず、もっと良い書き方あったら知りたいです。

では使ってみます。

  val jsonString =
    """
      |{
      |  "title": "Json4sの使い方",
      |  "content": "Json4sの使い方は...このようになってます",
      |  "datetime": "2019-01-12T03:15:00Z"
      |}
    """.stripMargin

  val json: JValue = parse(jsonString)

記事投稿のjsonからextractで変換します。

  implicit val formats = DefaultFormats + new PostSerializer()
  val post = json.extract[Post]

  println(post) // =>Post(Json4sの使い方,Json4sの使い方は...このようになってます,2019-01-12T12:15+09:00[Asia/Tokyo])

注意ですが、extractを使用したい場合formatsをimplicitで宣言する必要があります。

2019/01/15 追記

json4s 3.6以降の場合は下記のようにするとCustomSerializerを使用せず日付変換できますので、だいぶ簡単になりました。

  implicit val formats = DefaultFormats ++ JavaTimeSerializers.all

  val post = json.extract[Post]

まとめ

JsonからZonedDatetimeは単純にextractで抽出できなく、CustomSerialzerを定義してがんばります。以外に記述量多くなりました。もしかしたらcirceなどの他ライブラリだと短くなるかもしれないですね。

1
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?