kkkdev’s 開発 備忘録

残しておきたい開発情報を記録します。

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実装のライブラリを使う際にも必須レベルですし、短縮形の シンタックスがあっても困らない気がします。出ないかなあ。