circeは自動・半自動でJSONをcase classにデコードしてくれるのですが、今回やんごとなき事情で手動でデコードしたかったのでやってみましたメモ。今回やったのは公式ドキュメントのTraversing and modifying JSONを参考にしています。

対象のJSON


こんな感じのJSONがあったとします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[
{
"id": 98765,
"repo": {
"name": "hoge/hoge",
}
},
{
"id": 77889,
"repo": {
"name": "example/example",
}
},
{
"id": 55432,
"repo": {
"name": "foo/foo",
}
},
]

今回はこのidrepo.nameをcase classにデコードしてみます。デコード先のcase classは下記のようなものとします。

1
case class Data(id: Long, name: String)

実装


最終的にこんな感じになりました。

1
2
3
4
5
6
7
8
9
10
11
12
val events: Json = parse("ここにサンプルのJSONが来る")).getOrElse(Json.Null)
val hCursor: HCursor = events.hcursor

val data = for (jsons <- hCursor.values) yield {
for (json <- jsons.toList) yield {
val id: Long = json.hcursor.get[Long]("id").getOrElse(0)
val name: String = json.hcursor.downField("repo").get[String]("name").getOrElse("")

Data(id, name)
}
}
data.foreach(println)

これを実行すると下記のような結果が得られます。

1
2
3
Data(98765,"hoge/hoge")
Data(77889,"example/example")
Data(55432,"foo/foo")

以下、順に解説していきます。

Cursors


CursorとはcirceにおいてJSONを走査して抽出・編集するための機能です。circeには3種類のCursorがあります。公式ドキュメントのを頑張って翻訳してみると下記のような感じになります。

  • Cursor: JSONを走査して変更を加える機能を提供します。
  • HCursor: 操作の履歴を追跡します。これは何かうまくいかないときにエラーメッセージを提供するのに役立ちます。
  • ACursor: 操作の履歴も追跡しますが、失敗の可能性もあります。

たぶん、翻訳が間違っていなければこんな感じなんじゃないかと思います。実装はみてないです。ごめんなさい。今回は公式ドキュメントで使用されていたHCursorを使用しました。

JSON配列について


配列の扱いが公式ドキュメントに記載されていないのですが、JSONが配列だった場合はvaluesメソッドで取得できます。こいつはIteratableなので後はforとかで回せばよいです。こんな感じですね。

1
2
3
4
5
6
7
8
9
10
11
val events: Json = parse(SomeJson)).getOrElse(Json.Null)
val hCursor: HCursor = events.hcursor

val data = for (jsons <- hCursor.values) yield {
for (json <- jsons.toList) yield {
val id: Long = json.hcursor.get[Long]("id").getOrElse(0)
val name: String = json.hcursor.downField("repo").get[String]("name").getOrElse("")

Data(id, name)
}
}

これでdataにはList[Data]型の値が入ります。

値の取得


hcursor.get[T]Decoder.Result[T]型の値が取得できます。このDecoder.Result[T]はEitherなのですが、下記のようにしてgetOrElseでデフォルト値を指定して取得することができます。

1
2
3
4
5
6
val data = for (jsons <- hCursor.values) yield {
for (json <- jsons.toList) yield {
val id: Long = json.hcursor.get[Long]("id").getOrElse(0)
val name: String = json.hcursor.downField("repo").get[String]("name").getOrElse("")
}
}

Eiterですので、取得できたかどうかで下記のように処理を分けることもできます。

1
2
3
4
json.hcursor.downField("repo").get[String]("name") match {
case Right(r) => なにかの処理
case Left(l) => なにかの処理
}

感想


(日本語の)情報量が少なくてあまりやる気が出なかったのですが、ちょっとコードを読んだりするとなんとかなることがわかりました。まあ、この(日本語の)情報量が少なくてやる気が出ないのは毎回なのですが、毎回調べたらどうにかなるのでもうちょっとやる気を出して頑張ります。