swiftのOptional + flatMapで複雑なjsonをパースする
swiftの最大の利点といえばOptional型だと思いますが、 扱い方を間違えるとネストやif文がすぐに大量生産されてしまいます。
今回は、なるべくシンプルな記述で複雑なデータ構造を扱うことを検討してみます。
{ "foo": { "bar": [{ "baz": { "value": "aaaa" } }, { "baz": { "value": "bbbb" } }, { "baz": { "val": "cccc" } }, { "baz": { "value": null } }] } }
- 原則として、[foo] [bar]の中に[baz][value]の配列がある
- それ以外のデータは不正なフォーマットと見なして無視する
こんな感じの(httpで取得した)jsonをライブラリ無しでパースして、 いくつかの方法でvalue の値を取り出してみます。
jsonを取得し、NSDictionaryのOptionalに変換
var data:NSData? = NSURL(string:"(jsonを取得するURL)").flatMap{NSData(contentsOfURL: $0)} var jsonDic:NSDictionary? if let data_ = data { do{ jsonDic = (try NSJSONSerialization.JSONObjectWithData(data_, options: NSJSONReadingOptions.AllowFragments) as? NSDictionary) }catch{} } }
この辺はあまり本質とは関係ありません(準備段階)。 ここからどうデータ構造をパースしていくかを考えます。
Optional chaining を使ってみる
jsonDic?["foo"]?["bar"]
この時点でOptionalのネストにげんなりします。
Optional binding にしてみる
if let jsonDic_ = jsonDic, foo_ = jsonDic_["foo"], bar_ = foo_["bar"], bar2_ = bar_ as? NSArray{ let value = bar2_.flatMap{ $0["baz"] }.flatMap{ $0["value"] }.flatMap{ $0 as? String} print(value) }
正直、途中で放棄している感はあります。
flatMapを使う
swift2.0からの、モナド則を満たすflatMapを使ってみます。
let val = jsonDic.flatMap{$0["foo"]}.flatMap{ $0["bar"] as? NSArray }? .flatMap{ $0["baz"]}.flatMap{$0["value"] as? String} if let val_ = val { print(val_) }
ストリームを切ることなく、直列に記述できました。 (メソッドチェーンの状態にArrayとOptionalが混在しているので、直感的に理解しにくいかもですが...)
マルチコア時代のプログラマは関数脳になろう〜Java8のススメ〜
http://tech-sketch.jp/2013/08/parallel-functional-programming-1.html
JAVA8のストリーム同様、 関数型プログラミング的なアプローチに近づいています。
もはやflatMap無しで書くのはだいぶしんどいような... こういう際に今までどうやって書いてたか思い出せません。
Promise実装のライブラリを使う際にも必須レベルですし、短縮形の シンタックスがあっても困らない気がします。出ないかなあ。