CLOVER🍀

That was when it all began.

JacksonのScalaモジュールを試してみる

先ほどjson4sを試してみましたが、

json4sで遊ぶ
http://d.hatena.ne.jp/Kazuhira/20140419/1397895464

せっかくなのでJacksonのScalaモジュールも試してみたくなりまして。

jackson-module-scala
https://github.com/FasterXML/jackson-module-scala

こちらは、Jackson本家のScalaサポートになります。メンテナンス状況は大丈夫かなぁ?と思ったら、JacksonとScalaの最新(さすがに2.11はまだですが)に追従していっているので見ておくべきかなと。

では、いってみましょう。

準備

とりあえず、依存関係の定義を。
build.sbt

name := "jackson-module-example"

version := "0.0.1-SNAPSHOT"

scalaVersion := "2.10.4"

organization := "org.littlewings"

scalacOptions ++= Seq("-Xlint", "-deprecation", "-unchecked")

incOptions := incOptions.value.withNameHashing(true)

libraryDependencies ++= Seq(
  "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.3.3",
  "org.scalatest" %% "scalatest" % "2.1.3" % "test"
)

では、これに対してテストを書いていく感じで。
src/test/scala/org/littlewings/jackson/JacksonModuleSpec.scala

package org.littlewings.jackson

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule

import org.scalatest.FunSpec
import org.scalatest.Matchers._

class JacksonModuleSpec extends FunSpec {
  describe("JacksonModule") {
    // ここに、テストを書く!
  }
}

今回のサンプルでは、importするのは

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule

だけでよさそうです。

Case ClassをJSONと相互変換する

今回は、いきなりCase Classから。先ほどのjson4sの時と、同じCase Classを用意しました。
src/main/scala/org/littlewings/jackson/CaseClasses.scala

package org.littlewings.jackson

case class Organization(name: String, persons: List[Person])

case class Person(name: String, age: Int)

JSON文字列からCase Classへの変換。

    it("parse json as Case Class") {
      val json =
        """|{"name":"some organization",
           | "persons":
           |  [{"name":"Taro","age":22},
           |   {"name":"Hanako","age":18},
           |   {"name":"Saburo","age":25}]}""".stripMargin

      val mapper = new ObjectMapper
      mapper.registerModule(DefaultScalaModule)

      mapper.readValue(json, classOf[Organization]) should be (Organization("some organization",
                                                                            List(Person("Taro", 22),
                                                                                 Person("Hanako", 18),
                                                                                 Person("Saburo", 25))))
    }

Scalaモジュールを使う時は、JacksonのObjectMapperにScalaModuleを登録すればよいみたいです。

      val mapper = new ObjectMapper
      mapper.registerModule(DefaultScalaModule)

あとは、普通にObjectMapperとして使用します。

      mapper.readValue(json, classOf[Organization]) should be (Organization("some organization",
                                                                            List(Person("Taro", 22),
                                                                                 Person("Hanako", 18),
                                                                                 Person("Saburo", 25))))

Case ClassからJSONへの変換。

    it("produce json from Case Class") {
      val organization =
        Organization(
          "some organization",
          List(Person("Taro", 22), Person("Hanako", 18), Person("Saburo", 25))
        )

      val mapper = new ObjectMapper
      mapper.registerModule(DefaultScalaModule)

      mapper.writeValueAsString(organization) should be {
        """{"name":"some organization","persons":[{"name":"Taro","age":22},{"name":"Hanako","age":18},{"name":"Saburo","age":25}]}"""
      }
    }

ObjectMapperに対する設定は、JSONからCase Classに戻した時と同じです。

もうちょっとミュータブルな例で

ここで、json4sではうまくいかなかった、以下のようなクラスに対するマッピング
src/main/scala/org/littlewings/jackson/SimpleClasses.scala

package org.littlewings.jackson

class VarPerson {
  var name: String = _
  var age: Int = _
}

こちらだと、普通にうまくいきました。

    it("parse json use Var Class") {
      val json = """|{ "name": "Taro",
                    |  "age": 22}""".stripMargin

      val mapper = new ObjectMapper
      mapper.registerModule(DefaultScalaModule)

      val person = mapper.readValue(json, classOf[VarPerson])
      person.name should be ("Taro")
      person.age should be (22)
    }

    it("produce json from use Var Class") {
      val person = new VarPerson
      person.name = "Taro"
      person.age = 22

      val mapper = new ObjectMapper
      mapper.registerModule(DefaultScalaModule)

      mapper.writeValueAsString(person) should be ("""{"name":"Taro","age":22}""")
    }

Javaフレームワークとつなぐ時は、json4s以外にもこちらを検討してもよいかもですね。

Scalaのコレクションを使う

最後に、ScalaのコレクションとJSONの相互変換を。

JSONからScalaのコレクションへ。

    it("parse json use Scala Collection") {
      val json =
        """|{"name":"some organization",
           | "persons":
           |  [{"name":"Taro","age":22},
           |   {"name":"Hanako","age":18},
           |   {"name":"Saburo","age":25}]}""".stripMargin

      val mapper = new ObjectMapper
      mapper.registerModule(DefaultScalaModule)

      mapper.readValue(json, classOf[Map[_, _]]) should be {
        Map("name" -> "some organization",
            "persons" -> List(
              Map("name" -> "Taro", "age" -> 22), Map("name" -> "Hanako", "age" -> 18), Map("name" -> "Saburo", "age" -> 25)
          )
        )
      }
    }

Scalaのコレクションから、JSONへ。

    it("produce json from Scala Collection") {
      val organization =
        Map("name" -> "some organization",
            "persons" -> List(
              Map("name" -> "Taro", "age" -> 22), Map("name" -> "Hanako", "age" -> 18), Map("name" -> "Saburo", "age" -> 25)
            )
          )

      val mapper = new ObjectMapper
      mapper.registerModule(DefaultScalaModule)

      mapper.writeValueAsString(organization) should be {
        """{"name":"some organization","persons":[{"name":"Taro","age":22},{"name":"Hanako","age":18},{"name":"Saburo","age":25}]}"""
      }
    }
  }

これこれで、導入は簡単そうで良いと思います。