[Swift] ObjectMapper で JSON データのマッピング

こんにちは。

なんつうかもう、気付いたらそこら中 JSON ばっかりですね。サーバから JSON のデータを持ってきてパースしてる人は世界中にどんだけいるんでしょーか。

取得した JSON は、データ構造に応じて適切にモデルクラスに落とし込みたいですよね。ですよね？ Swift でどうやるか調べたところ、ObjectMapper を使うとそんなこんながとても簡単に実現できました。こいつはなかなか便利な奴です。

 * https://github.com/Hearst-DD/ObjectMapper

Alamofire と組み合わせたり Realm と組み合わせたりもできて素敵です。今回は書いてませんが、JSON のキャッシュとして Realm と一緒に使ってます。あんまり意識せずにすんなり使えるのがいいですね。

# セットアップ

CocoaPods や Carthage でインストールできますので Readme をご参考に

# 基本的な使い方

たとえば、ユーザデータが入った JSON があるとして。

```json
[
    { "name": "takayama" },
    { "name": "takahashi" },
    { "name": "yamada" }
]
```

これに対応するクラスを作って。

```swift
class User: Mappable {
    var name: String?

    required init?(_ map: Map) {
    }

    func mapping(map: Map) {
        name <- map["name"]
    }
}
```

こんな風に読み込む。

```swift
let users: [ User ]? = Mapper<User>().mapArray(jsonString)
print(users?[0].name) // => Optional("takayama")
```

たとえば JSON の中に `name` がなかった場合は user.name の値は `nil` になりますけど、`var name: String = ""` とかやっておけば空文字になりますし、`var name: String = "Default"` とかだと特定の値をデフォルト値にできます。

まあ基本的な使い方はオリジナルのドキュメント見ればわかります。ここまでは簡単です。

# 型変換

例えば JSON のデータ方が文字列型だった場合に数値型に変換してくれるような仕組みやらがあるのですが、この辺がなかなか理解できなくて苦労しました。特に難しかったのが `NSDate` への変換です。

`Custom Transforms` という仕組みがあって、ドキュメントを読むと `DateTransform` というのを使って `NSDate` に対応しているように見えます。ただこれ、データソースが UnixTime を想定しているんですよね。1456727500 的な。我々のデータは `yyy-MM-dd HH:mm:ss` なのでこのままでは無理ゲー。

```swift
class User: Mappable {
    var birthday: NSDate?

    required init?(_ map: Map) {

    }

    func mapping(map: Map) {
        birthday <- (map["birthday"], DateTransform())
    }
}
```

`Custom Transforms` まわりのソースを参照するとサブクラスがいくつかあって、その中の `CustomDateFormatTransform` というやつがそのものずばりで使えました。TransformType クラスを継承すれば自作もできそうです。

 * https://github.com/Hearst-DD/ObjectMapper/tree/master/ObjectMapper/Transforms

そんでもって `CustomDateFormatTransform` を使うとこんな簡単に…！

```swift
class User: Mappable {
    var birthday: NSDate?

    required init?(_ map: Map) {

    }

    func mapping(map: Map) {
        birthday <- (map["birthday"], CustomDateFormatTransform(formatString: "yyyy-MM-dd HH:mm:ss"))
    }
}
```

他に `URLTransform` を使うと `NSURL` にできます。

```swift
var url: NSURL?

func mapping(map: Map) {
    url <- (map["url"], URLTransform())
}
```

EnumTransform というのもあります。

```swift
enum DataType: Int {
    case Unknown, Work, Home
}

...

var type: DataType?

func mapping(map: Map) {
    type <- (map["type"], EnumTransform<DataType>())
}
```

# 全部入り

今回紹介したやつで作るとこんな感じでしょうか。

```swift
class User: Mappable {
    enum DataType: Int {
        case Unknown, Work, Home
    }

    var name: String?
    var birthday: NSDate?
    var type: DataType = .Unknown
    var url: NSURL?

    required init?(_ map: Map) {
    }

    func mapping(map: Map) {
        name     <- map["name"]
        birthday <- (map["birthday"], CustomDateFormatTransform(formatString: "yyyy-MM-dd HH:mm:ss"))
        type     <- (map["type"], EnumTransform<DataType>())
        url      <- (map["url"], URLTransform())
    }
}
```

JSON

```json
[
{
    "name": "takayama",
    "birthday": "1999-09-09 00:00:00",
    "type": 2,
    "url": "http://twitter.com/takayama"
},
{
    "name": "takahashi",
    "birthday": "1999-01-01 00:00:00",
    "type": 1,
    "url": "http://www.example.com/"
},
{
    "name": "yamada",
    "birthday": "2016-02-29 15:20:00",
    "type": 2,
    "url": "http://qiita.com/takayama"
}
]
```

実行してみる。

```swift
let users: [ User ]? = Mapper<User>().mapArray(jsonString)

for user in users! {
   print(user.name, user.birthday, user.type.rawValue, user.url)
}
```

出力結果。

```
Optional("takayama") Optional(1999-09-08 15:00:00 +0000) 2 Optional(http://twitter.com/takayama)
Optional("takahashi") Optional(1998-12-31 15:00:00 +0000) 1 Optional(http://www.example.com/)
Optional("yamada") Optional(2016-02-29 06:20:00 +0000) 2 Optional(http://qiita.com/takayama)
```