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
には他の二つと二つの大きな違いがあります。一つは値の代入で、残りの二つが値渡しなのに対して参照渡しであること。そしてもう一つが、継承をサポートしていることです。
struct
やenum
にも、新たなプロパティを追加するのはextension
で可能です。が、これだとオールオアナッシングなんですよね。サイトごとにschemaを変えたいってのが出来ない。
この問題があるので、Array
やDictionary
にもclass版が欲しいところではありますね。
Dan the Man with too Much Data to (De)?serialize
追記[2016.01.29]: Swift 2にも対応しているのみならず、Playgroundも付いています。