こんにちは。
なんつうかもう、気付いたらそこら中 JSON ばっかりですね。サーバから JSON のデータを持ってきてパースしてる人は世界中にどんだけいるんでしょーか。
取得した JSON は、データ構造に応じて適切にモデルクラスに落とし込みたいですよね。ですよね? Swift でどうやるか調べたところ、ObjectMapper を使うとそんなこんながとても簡単に実現できました。こいつはなかなか便利な奴です。
Alamofire と組み合わせたり Realm と組み合わせたりもできて素敵です。今回は書いてませんが、JSON のキャッシュとして Realm と一緒に使ってます。あんまり意識せずにすんなり使えるのがいいですね。
セットアップ
CocoaPods や Carthage でインストールできますので Readme をご参考に
基本的な使い方
たとえば、ユーザデータが入った JSON があるとして。
[
{ "name": "takayama" },
{ "name": "takahashi" },
{ "name": "yamada" }
]
これに対応するクラスを作って。
class User: Mappable {
var name: String?
required init?(_ map: Map) {
}
func mapping(map: Map) {
name <- map["name"]
}
}
こんな風に読み込む。
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
なのでこのままでは無理ゲー。
class User: Mappable {
var birthday: NSDate?
required init?(_ map: Map) {
}
func mapping(map: Map) {
birthday <- (map["birthday"], DateTransform())
}
}
Custom Transforms
まわりのソースを参照するとサブクラスがいくつかあって、その中の CustomDateFormatTransform
というやつがそのものずばりで使えました。TransformType クラスを継承すれば自作もできそうです。
そんでもって CustomDateFormatTransform
を使うとこんな簡単に…!
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
にできます。
var url: NSURL?
func mapping(map: Map) {
url <- (map["url"], URLTransform())
}
EnumTransform というのもあります。
enum DataType: Int {
case Unknown, Work, Home
}
...
var type: DataType?
func mapping(map: Map) {
type <- (map["type"], EnumTransform<DataType>())
}
全部入り
今回紹介したやつで作るとこんな感じでしょうか。
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
[
{
"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"
}
]
実行してみる。
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)