LoginSignup
472
466

More than 5 years have passed since last update.

Swiftで面倒なJSONの取り扱いをさらに10倍便利にするclass JSON

Last updated at Posted at 2014-07-15

Swiftで面倒なJSONの取り扱いを10倍便利にするSwiftyJSONをもってしても、まだJavaScriptよりも面倒だったのが悔しかったので書きました。

Synopsis

AnyObjectなSwiftオブジェクトも、こうして JSON Stringにできます。

let obj:[String:AnyObject] = [
    "array": [JSON.null, false, 0, "",[],[:]],
    "object":[
        "null":   JSON.null,
        "bool":   true,
        "int":    42,
        "double": 3.141592653589793,
        "string": "a α\t\n?",
        "array":  [],
        "object": [:]
    ],
    "url":"http://blog.livedoor.com/dankogai/"
]

let json = JSON(obj)
json.toString()
// "{\"array\":[null,false,0,\"\",[],{}],
//  \"object\":{\"int\":42,\"double\":3.141592653589793,
//  \"string\":\"a α\t弾\n?\",\"object\":{},\"null\":null,
//  \"bool\":true,\"array\":[]},
//  \"url\":\"http://blog.livedoor.com/dankogai/\"}"

もちろん文字列からも

let json = JSON.parse("{\"array\":[...}")

URLからも。

let json = JSON.fromURL("http://api.dan.co.jp/jsonenv")

JSONの歩き方。

添字で葉までいって、型をあわせるだけ。

json["object"]["null"].asNull       // NSNull()
json["object"]["bool"].asBool       // true
json["object"]["int"].asInt         // 42
json["object"]["double"].asDouble   // 3.141592653589793
json["object"]["string"].asString   // "a α\t弾\n?"

json["array"][0].asNull             // NSNull()
json["array"][1].asBool             // false
json["array"][2].asInt              // 0
json["array"][3].asString           // ""

SwiftyJSONと同様、存在しない添字にアクセスしても爆発しません。その代わり発生したエラーをほいほいと次のメソッドに投げていきます。Optional ChainならぬNSError Chainといったところでしょうか。

if let b = json["noexistent"][1234567890]["entry"].asBool {
    // ....
} else {
    let e = json["noexistent"][1234567890]["entry"].asError
    println(e)
} // Error Domain=JSONErrorDomain Code=404 "["noexistent"] not found" UserInfo=0x10064bfc0 {NSLocalizedDescription=["noexistent"] not found}

ここまではSwiftyJSONと同様なのですが、一つ困ったことがあったのです。

SwiftyJSONのJSONValueは、enumだったのです。

なんでenumだと困るのでしょう?

class JSONは継承可

ここでJavaScriptにおけるJSONの取り扱いを見てみましょう。

//json["object"]["string"] vs...
  json.object.string

はい、さらに短い。オブジェクトの一分岐ごとに4文字も。Swift(というか厳密にはNSDictionary)では添字を使わざるを得なかったところを、JavaScriptではプロパティで行けるからです。

でもプロパティの追加は、何もJavaScriptの専売特許ではありませんよね?

こうすればいけるではありませんか?

//// schema by subclassing
class MyJSON : JSON {
    override init(_ obj:AnyObject){ super.init(obj) }
    override init(_ json:JSON)  { super.init(json) }
    var null  :NSNull? { return self["null"].asNull }
    var bool  :Bool?   { return self["bool"].asBool }
    var int   :Int?    { return self["int"].asInt }
    var double:Double? { return self["double"].asDouble }
    var string:String? { return self["string"].asString }
    var url:   String? { return self["url"].asString }
    var array :MyJSON  { return MyJSON(self["array"])  }
    var object:MyJSON  { return MyJSON(self["object"]) }
}

行けました!

let myjson = MyJSON(obj)
myjson.object.null      // NSNull?
myjson.object.bool      // Bool?
myjson.object.int       // Int?
myjson.object.double    // Double?
myjson.object.string    // String?
myjson.url              // String?

JavaScriptではゼロ円でやってくれる連想配列のプロパティ化のために、一行メソッドの羅列とはいえclassを一個定義するのはめんどくさくも感じます。その一方、ソースコードできちんと定義しておくことじゃ、意図しないプロパティをtypoで作ってしまうことがありえないことも意味します。そもそもコンパイル通りませんし、IDEによる補完もこれで効くようになります。さらに各プロパティ(というかgetter)で入力検証したりするのもこれで自由自在です。

Swiftにはプリミティブでない型が三種類もあります。が、classには他の二つと二つの大きな違いがあります。一つは値の代入で、残りの二つが値渡しなのに対して参照渡しであること。そしてもう一つが、継承をサポートしていることです。

structenumにも、新たなプロパティを追加するのはextensionで可能です。が、これだとオールオアナッシングなんですよね。サイトごとにschemaを変えたいってのが出来ない。

この問題があるので、ArrayDictionaryにもclass版が欲しいところではありますね。

Dan the Man with too Much Data to (De)?serialize

追記[2016.01.29]: Swift 2にも対応しているのみならず、Playgroundも付いています。

472
466
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
472
466