Android/ScalaでJSONパース - jackson-databind編

ScalaのJSONライブラリには[こちら](http://stackoverflow.com/questions/8054018/json-library-for-scala?answertab=active#tab-top)のスレッドによると、ざっと下の選択肢がある。多い...

1. [parsing.json.JSON](http://www.scala-lang.org/api/current/index.html#scala.util.parsing.json.JSON%24) - 標準ライブラリ
2. [spray-json](https://github.com/spray/spray-json)
3. [Jerkson](https://github.com/codahale/jerkson) - JacksonのScala用ラッパ
4. [sjson](https://github.com/codahale/jerkson)
5. [lift-json](https://github.com/lift/lift/tree/master/framework/lift-base/lift-json/)
6. [json4s](https://github.com/json4s/json4s) - lift-json系
7. [Argonaut](http://argonaut.io/) - Scalaz系
8. [play-json](http://www.playframework.com/documentation/2.2.x/ScalaJsonInception)
9. [Jackson](https://github.com/FasterXML/jackson) - Javaライブラリ

今回は、とっつきやすそうで、pull型(Streaming)パースをサポートしていて、Java界で実績がありそうなJacksonを試すことにした。環境は[sbt-android](http://qiita.com/ikuo/items/53f5b300f09c38a6c952)で、とりあえずJSONをScalaのオブジェクトとして取得(データバインド）してみることにした。

## 1. 依存関係の追加
build.sbtに jackson-core, jackson-databind を追加。

```scala
libraryDependencies += "com.fasterxml.jackson.core" % "jackson-core" % "2.3.0"

libraryDependencies += "com.fasterxml.jackson.core" % "jackson-databind" % "2.3.0"
```

## 2. モデルクラスの定義
引数無しコンストラクタと、プロパティごとにset*系のメソッドを定義する。

```scala
package com.example.app1.model

class MyModel1(
  var prop1: String,
  var prop2: Int
) {
  def this() = { this(null, -1) }
  def setProp1(a: String) { this.prop1 = a }
  def setProp2(a: Int) { this.prop2 = a }
}
```
JSONの生成もする場合はget*系も定義をする（略）。

## 3. JSONの読み込み例
```scala
package com.example.app1
import com.fasterxml.jackson.databind.ObjectMapper
import com.example.app1.model.MyModel1

class Class1 {
  def method1 = {
    ...
    val json = """{"prop1": "value1", "prop2": 1234}"""
    val model1 = (new ObjectMapper).readValue(json, classOf[MyModel1])
    model1.prop1 // => "value1"
  }
}
```

## 4. proguardの設定
データバインドの際に使われるクラス名などをproguardの難読化の対象から外すため、proguard.cfgに下を追加する。

```
-keep class com.fasterxml.jackson.databind.** { *; }
-keepattributes *Annotation*,EnclosingMethod
-keep public class com.example.app1.model.** {
  public void set*(***);
  public *** get*();
}
```

1つめはjackson-databindのパッケージを、サブパッケージ含めて全て難読化対象外にするもの。
2つめはjacksonが使っているアノテーションとenclosing methodを難読化対象外にするもの。Javaでは[メソッドの中にローカルなクラスを定義できる](http://www.tutorialspoint.com/java/lang/class_getenclosingmethod.htm)らしく(enclosing method)、Jacksonがこれを使っている。[jackson-databindのソース](https://github.com/FasterXML/jackson-databind)を`getEnclosingMethod`でgit grepすると、この機能(enclosing method)が使われているのが分かる。
3つめは`MyModel1`などモデルクラスとそのget*系, set*系メソッドを難読化対象外にするもの。

## 5. apk生成
apkファイルを生成する際、 compile:dx フェーズで`trouble writing output: excess write of ...`というエラーが出た。

```
sbt> apk
...
trouble writing output: excess write of 16
[trace] Stack trace suppressed: run last App2/compile:dx for the full output.
[error] (App2/compile:dx) Nonzero exit value: 2
[error] Total time: 17 s, completed Feb 22, 2014 10:06:32 AM
```

project/Build.scala で`analysisBudget`を512から4096に増やすと解消した。[こちら](https://github.com/scala/scala/pull/953)によると、Scalaコンパイラがソースコードの到達可能性・条件網羅性の検査(?)に最大どれくらい時間をかけるかというパラメータらしい...

```scala
object AndroidBuild extends Build {
  ...
  val globalSettings = Seq (
    ...
    initialize ~= { _ =>
      sys.props("scalac.patmat.analysisBudget") = "4096"
    }
  )

  lazy val main = AndroidProject (
    ...
    settings = globalSettings
  )
}
```

## 6. 動作確認
[こちら](https://gist.github.com/ikuo/9148466)のテスト用MainActivityを書いて実行したところ、無事に"value1"が表示された。