目的
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などの他ライブラリだと短くなるかもしれないですね。