Scala : Play JSONを使う

前回のApache Commons CSVでのCSVの処理

mi12cp.hatenablog.com

に引き続き、今回はJSONを扱っていきます。

JSONのライブラリはplayframeworkに含まれるPlay JSONを用いることにしました。 playframeworkを用いなくても、これ単独でパッケージを追加することが出来ます。

パッケージの追加

build.sbt

libraryDependencies += "com.typesafe.play" %% "play-json" % "2.6.7"

使い方

インポート

import play.api.libs.json.Json

モデルの作成

case class Comment(id: Int, comment: String, replies: List[Comment])
object Comment {
  implicit val format = Json.format[Comment]
}

利用するJSONのフォーマットが予め決まっている場合は、 モデルとなるケースクラスを作成し、 Json.parse, Json.toJsonで利用するJsValue型と利用したい型への暗黙の型変換を宣言します。

プログラム内部での利用

val str = """{"id": 1,"comment": "hoge","replies": []}"""

// String -> JsValue -> Comment
val comment: Comment = Json.parse(str).validate[Comment].get

// Comment -> JsValue -> String
val strSimple: String = Json.stringify(Json.toJson(comment))
val strReadable: String = Json.prettyPrint(Json.toJson(comment))

Jsonクラス内ではJsValueやJsString, JsNumber, JsArray等のデータ型を利用しているのですが、 このように紋切り型を書くだけでScala標準のデータ構造として利用できるのが非常に便利です。

簡単なプログラムの作成

ここでは、下記のjsonファイルを読み取り、 ネストされたオブジェクトを深さ優先でフラットにして出力することを行います。

id, comment, repliesの組をComment型としています。Comment型はrepliesにて再帰的に指定されていますが、難なくJSONからパースできます。

プログラム

jsonExample.json

{
  "id": 1,
  "comment": "hoge",
  "replies": [
    {
      "id": 2,
      "comment": "fuga",
      "replies": [
        {
          "id": 3,
          "comment": "piyo",
          "replies": []
        },
        {
          "id": 4,
          "comment": "hogera",
          "replies": []
        }
      ]
    },
    {
      "id": 5,
      "comment": "hoge",
      "replies": []
    }
  ]
}

Main.scala

import scala.io.Source
import play.api.libs.json.Json

case class Comment(id: Int, comment: String, replies: List[Comment]) {
  def flatten: List[Comment] = {
    def proc(comment: Comment): List[Comment] = {
      if (comment.replies.isEmpty)
        comment :: Nil
      else
        Comment(comment.id, comment.comment, Nil) :: comment.replies.flatMap(c => proc(c))
    }
    proc(this)
  }
}
object Comment {
  implicit val format = Json.format[Comment]
}

object Main extends App {

  val str = Source.fromFile("JsonExample.json").getLines.mkString("\n")
  val comment = Json.parse(str).validate[Comment].get
  val flatComments = comment.flatten
  val commentsStr = Json.prettyPrint(Json.toJson(flatComments))
  println(commentsStr)

}

terminal output

[ {
  "id" : 1,
  "comment" : "hoge",
  "replies" : [ ]
}, {
  "id" : 2,
  "comment" : "fuga",
  "replies" : [ ]
}, {
  "id" : 3,
  "comment" : "piyo",
  "replies" : [ ]
}, {
  "id" : 4,
  "comment" : "hogera",
  "replies" : [ ]
}, {
  "id" : 5,
  "comment" : "hoge",
  "replies" : [ ]
} ]

備考

プログラム内のflatCommentsはList[Comment]型だが、これをtoJsonする際に

case class Comments(comments: List[Comment])
object Comments {
  implicit val format = Json.format[Comments]
}

のようにしてComments型を用意する必要はない。

Comment型の暗黙の型変換さえあれば、List[Comment]型をそのままtoJsonできる。

逆に、上記Main.scalacommentsStrを用いて

val comments = Json.parse(commentsStr).validate[List[Comment]].get

とすることで、List[Comment]型のcommentsを取得することも可能である。

todo

  • 実際に運用する際にはパースエラーの考慮をしないと行けないが、ここでは行っていない

参考