定食屋おろポン

おろしポン酢と青ネギはかけ放題です

SwiftでJSONを扱うライブラリ SwiftyJSON | json-swift

SwiftJSONを扱う「つらみ」を解消するライブラリがトレンドにあがってたのでながめてみます。

つらみ

SwiftJSONを扱うとなったとき、まず頭に浮かぶのがFoundationフレームワークNSJSONSerializationです。 JSONを渡すと、パースしてNSDictionaryNSArrayのオブジェクトにしてくれます。個々の要素はNSString,NSNumber,あるいはNSNullといったオブジェクトに変換されます。

SwiftではObjective-Cのオブジェクトを扱うことができるので、NSJSONSerializationを使ってパースしたらSwiftでもJSONを扱えるはずです。

さて、ではコレをつかって書いてみましょう。サンプルにはTwitter APIのサンプルレスポンスを使います。

https://dev.twitter.com/docs/api/1.1/get/users/show

ここから、screen_nameと、status/entities/urls/[0]/urlを取ってみます。

import Foundation

let sampleJSONData :NSData = NSData(contentsOfFile: "/path/to/sampleJSON.json")
let sampleJSON :Dictionary = NSJSONSerialization.JSONObjectWithData(sampleJSONData, options: nil, error: nil) as NSDictionary

var screen_name :String
var url :String

// Get screen_name
screen_name = sampleJSON["screen_name"] as NSString

// Get status/entities/urls/[0]/url
if let status :Dictionary = sampleJSON["status"] as? NSDictionary {
    if let entities :Dictionary = status["entities"] as? NSDictionary {
        if let urls :Array = entities["urls"] as? NSArray {
            if let urlSet :Dictionary = urls[0] as? NSDictionary {
                if let anUrl :String = urlSet["url"] as? NSString {
                    url = anUrl
                }
            }
        }
    }
}

書けました!

つ、つらい。。キャストつらいし、なんていうかつらい。*1 全てid型にぶっ込むObjective-Cのつらみとも言えます。*2

では、いわゆるObject型ではなくてSwiftDictionaryArrayStringNumberJSONを持てば良いのでは?とも思うのですが、そうは問屋が卸しません。

SwiftではArrayDictionaryも、宣言した型の要素しか入れることはできません。Array<String>や、Dictionary<String, Number>などです。 「なんでもid型だからプリミティブじゃなければ何でも突っ込んでいーよ」っていうObjective-bitchとは違うんです。*3

そんなつらみがあります。

解決方法

Swiftには優秀なEnumがあります。EnumDictionaryArrayStringNumberをラップしてあげれば解決です。

SwiftyJSON

こんなかんじ

let sampleJSONData :NSData = NSData(contentsOfFile: "/path/to/sampleJSON.json")
let sampleJSON :JSONValue = JSONValue(sampleJSONData)
let screen_name :String? = sampleJSON["screen_name"].string
let url :String? = sampleJSON["status"]["entities"]["urls"][0]["url"].string

簡潔な記述がよいかんじです。列挙型JSONValueでラップし、.string.integerなどでアンラップします。

json-swift

こんなかんじ

let sampleJSONStringObj :String = NSString(contentsOfFile: "/path/to/sampleJSON.json")
let sampleJSON :JSON? = JSON.parse(sampleJSONStringObj)

var screen_name :String?
var url :String?

if let json :JSON = sampleJSON {
    screen_name =  json["screen_name"]?.string
    url = json["status"]?["entities"]?["urls"]?[0]?["url"]?.string
}

SwiftyJSONと大して変わりません。列挙型JSONでラップし、.string.numberなどでアンラップします。

違い

json-swiftの方はサンプルコードを見ての通り、?を多用するスタイルです。subscript(添字アクセス)がnil返すかもしれないってのがパッと見て分かるって利点はあるかもしれないですが、面倒くさいですね。

SwiftyJSONは、パースや添字アクセスには常にJSONValueを返す(要素が無かったりしたらJSONValue.JInvalidが入ります)ので、いちいち?をつける必要がありません。

まとめ

Enumよい

*1:うおおおお、Objective-Cより簡潔で読みやすい!!!と思わなくもないけど

*2:気づいたら同じことがSwiftyJSONのREADMEに書いてありました。悲しい。

*3:僕はObjective-C好きですよ