倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

Scala で Gson を使ってみる

ちょっと設定を記述するプロパティファイル的なものを使いたかったんだけど、Java のプロパティファイル使うのも XML 地獄に突入するのも嫌だったので、JSON を使って見ることに。 Java/Scala でどんなライブラリがよく使われているのか全然知らないんだけど、Google が開発してる Gson というライブラリがあったので使ってみました(バージョン 2.6.2)。

JSON のプリミティブ型に関しては、Scala で使う分には普通の Java プログラムと大差ない(と思う)のでまぁいいとして、JSON の配列を Scala のコレクションでどう扱うか(扱えるか)というのを見ていこうと思ってたんですが、どうにもうまく Scala のコレクションで動かないので、結局 Array (よって Java の配列)を使うのが無難だ、という結論。 Java のコレクションでもいいですが。

この記事の目次

参考

Java のコレクションを使う

まずは Java のコレクションを使ってみましょう。 Gson の基本的な使い方は

  1. Gson オブジェクトのインスタンスを生成
  2. Java オブジェクト <=> JSON 間の変換を実行
    • Gson#toJson() メソッドで Java オブジェクト => JSON の変換
    • Gson#fromJson() メソッドで JSON の変換 => Java オブジェクトの変換

を行います。 fromJson メソッドには、変換後の Java オブジェクトの型を指定する必要があります。

import java.util
import java.util.Arrays.asList
import com.google.gson.Gson

object Main extends App{
  // 準備
  val ints = asList(1, 2, 3, 4, 5)
  val gson = new Gson

  // Java/Scala オブジェクト => Json (java.lang.String)
  val json = gson.toJson(ints)
  println(json1)  // 「[1,2,3,4,5]」と表示

  // Json (java.lang.String) => Java/Scala オブジェクト
  val result1: util.List[Int] = gson1.fromJson(json1, classOf[util.List[Int]])
  println(result1)  // 「[1.0, 2.0, 3.0, 4.0, 5.0]」と表示
}

Scala で Java の List 型を参照する場合には、java.util をインポートして util.List とする慣習でしたね(というか IntelliJ IDEA さんが強制的にそうするの)。

ネット上で Gson のサンプルコードを検索すると、List ではなく ArrayList のような具象クラスを指定していますが、少なくとも現行バージョンの Gson では具象クラスでない util.List を指定してもキチンとインスタンス化してくれるようです。 また、TypeToken を使ってるサンプルコードもよく見かけますが、Class オブジェクトを指定するだけで充分です。 むしろ、TypeToken を使おうとすると型パラメータの指定(もしくはオブジェクトのキャスト)が必要になるので面倒:

  import com.google.gson.reflect.TypeToken

  val cType = new TypeToken[util.List[Int]](){}.getRawType  // getType では動かない!
  val result1: util.List[Int] = gson1.fromJson[util.List[Int]](json1, cType)

Java のコレクション使う方法はこんな感じ。 まぁ、util. 接頭辞を付けるって以外に特に面倒さや問題はないので、この方法を使うのもアリ。

Java/Scala の配列を使う

では次は配列を使ってみましょう。 Scala の配列は実質的に Java の配列なのでシームレスにストレス無く使えます。

import com.google.gson.Gson

object Main extends App{
  // 準備
  val ints = Array(1, 2, 3, 4, 5)
  val gson = new Gson

  // 配列 => JSON
  val json = gson.toJson(ints)
  println(json)  // 「[1,2,3,4,5]」と表示

  // JSON => 配列 その1
  val result1: Array[Int] = gson2.fromJson(json, classOf[Array[Int]])
  println(result1.mkString("Array(", ", ", ")"))  // 「Array(1, 2, 3, 4, 5)」と表示

  // JSON => 配列 その2
  val arrayType = new TypeToken[Array[Int]](){}.getType
  val result2: Array[Int] = gson2.fromJson[Array[Int]](json, arrayType)
  println(result2.mkString("Array(", ", ", ")"))  // 「Array(1, 2, 3, 4, 5)」と表示

「JSON => 配列 その2」は TypeToken を使ったサンプルコードです。 配列の場合は Java コレクションの場合と違って getType() でも getRawType() でも動きます*1が、getType() を使った場合はやはり型パラメータの指定もしくはオブジェクトのキャストが必要です。

配列をプロパティに持つ独自クラスを使う

上記の例はトップレベル要素が配列の場合のサンプルでしたが、プロパティとして配列を持つ独自クラスでも適切に処理してくれます。

まずは以下のような Person クラスを定義します:

case class Person(name: String, age: Int, alias: Array[String])

Person クラスは配列型のプロパティ alias を持ちます。 この Person クラスを踏まえて、以下のように JSON との変換を行えます:

import com.google.gson.Gson

object Main extends App{
  // 準備
  val gson = new Gson
  val waman = Person("waman", 100, Array("god", "devil"))

  // Person => JSON
  val json = gson.toJson(waman)
  println(json)  // 「{"name":"waman","age":100,"alias":["god","devil"]}」と表示

  // JSON => Person
  val result: Person = gson2.fromJson(json, classOf[Person])
  println(result)  // 「Person(waman,100,[Ljava.lang.String;@25a98f)」と表示
}

ケースクラスは toString を呼び出したときに各プロパティの toString を呼びますが、配列に対して toString を呼ぶと要素の情報を表示してくれないのがこの方法の残念なところ。 必要なら Person クラスの toString をオーバーライドするしかないですね。

Java のコレクションを使う
同様のことを配列ではなく Java のコレクションでやると

imoprt java.util
import java.util.Arrays.asList

case class Person(name: String, age: Int, alias: util.List[String])

object Main extends App{
  // 準備
  val gson = new Gson
  val waman = Person("waman", 100, asList("god", "devil"))

  // util.List => JSON
  val json = gson.toJson(waman)
  println(json)  // 「{"name":"waman","age":100,"alias":["god","devil"]}」と表示

  // JSON => util.List
  val result: Person = gson.fromJson(json, classOf[Person])
  println(result)  // 「Person(waman,100,[god, devil])」と表示
}

Java の List を使うと toString の文字列が見やすくていいですね。 配列よりこっちの方がいいのかな?

デフォルト値
書き出すオブジェクトのフィールドが null だったり読み込む JSON の要素に省略があったりした場合、例外が投げられるわけではなく、値の型に応じてデフォルト値が設定されます。

  val gson = new Gson

  // Java オブジェクト => JSON
  val waman = Person(null, 0, null)
  val json = gson.toJson(waman)
  println(json)  // 「{"age":0}」と表示(値が null のプロパティは書き出されない)

  // JSON => Java オブジェクト
  val result: Person = gson.fromJson("{}", classOf[Person])
  println(result)  // 「Person(null,0,null)」と表示(要素がないプロパティは null や0などが設定される)

ちなみに、Person クラスのコンストラクタにデフォルト値を入れても無視されます。

  case class(name: String = "waman", age: Int = 100, alias: Array[String])

  val result: Person = gson.fromJson("{}", classOf[Person])
  println(result)  // 「Person(null,0,null)」と表示される(コンストラクタのデフォルト値は無視)

まぁ、Gson は Java のライブラリなので当然ですが。

JSON の配列がネストしてても問題なく動きます。 配列と Java のコレクションが混ざってても大丈夫。

Scala のコレクションを使・・・えない?

一応、Gson では Java 標準の配列やコレクション以外のコンテナクラスも使えるようにしてあるみたいですが(UserGuide 参照)、どうにもうまく Scala で動かない・・・

  • JsonSerializer
  • JsonDeserializer
  • InstanceCreator

あたりの実装クラスを作ればいいはずなんですが・・・

Scalaスケーラブルプログラミング第2版

Scalaスケーラブルプログラミング第2版

  • 作者: Martin Odersky,Lex Spoon,Bill Venners,羽生田栄一,水島宏太,長尾高弘
  • 出版社/メーカー: インプレスジャパン
  • 発売日: 2011/09/27
  • メディア: 単行本(ソフトカバー)
  • 購入: 12人 クリック: 235回
  • この商品を含むブログ (46件) を見る

*1:これは、Java では配列の要素の型が実質的に型パラメータではないのが原因かと。