Swift2のJSONパーサ?な Decodable のソースを読む


# はじめに
https://github.com/Anviking/Decodable は、NSDictionaryやNSArrayからオブジェクトを取得するのに便利なライブラリです。

githubのDescriptionやpod searchで見ると、

```
Swift 2 JSON parsing done (more) right
```
とあるので、JSONのパース処理での利用が想定されているようです。ただ実際には、NSJSONSerializationで取得できるNSDictionaryやNSArrayから、簡単に値を取得する点に特化しています。はたしてこれがJSONのパーサなのか？とは思います。

このDecodableを使うと、NSDictionaryやNSArrayからの値の取得が、大変楽になります。

このDecodableは、5ファイルで合計300行程度と、とてもコンパクトな構成になっています。この中身はどうなっているのか？どうやって実現しているのか？と思い調べてみました。
このソース、読めば読むほど頭が混乱する、とても歯ごたえがありました。この記事は、今現在での理解をそのまま書いてみた、というものです。

そのため、Decodableのインストール方法や使い方は割愛します。また、自分も勘違いして間違った事も書いているかもしれません。その点はご容赦ください。


# Decodableの使用例
Decodableの特徴は、NSDictionaryからオブジェクトを生成する場面で、```as?```によるキャストが無くなる点と、値の取得に```=>```オペレータを使う点です。
これは、```=>```オペレータの戻り値の型情報から、型推論により、どの```=>```オペレータが動くのかが決まっているようです。その結果、```as?```によるキャストが不要となっています。

```

let dict: NSDictionary = [
    "str": "hoge",
    "int":123,
    "dic":["1":1,"2":2,"3":3],
    "ary":["xxx","yyy","zzz"]
]
// ↑から
// ↓のインスタンスを作りたい。
truct MyClass{
    let str:String
    let int:Int
    let dic:[String:Int]
    let ary:[String]
    let opt:String?
}

// Decodableを使わない場合
var myObj:MyClass?
if let s = dict["str"] as? String,     // ここで、
    i = dict["int"] as? Int,           // 「何にキャストするのか」
    d = dict["dic"] as? [String:Int],  // をずらずら書く
    a = dict["ary"] as? [String]       // 必要がある
{
    myObj = MyClass(
        str: s,
        int: i,
        dic: d,
        ary: a,
        opt: let o = dict["opt"] as? String)  // Optionalは別になってしまう
}
print(myObj)


// Decodableを使った場合
// コンストラクタの型情報から、
// 型推論によって「何にキャストするのか」を
// 省略できてスッキリしている
let myObj2:MyClass? = try? MyClass(
    str:dict => "str",
    int:dict => "int",
    dic:dict => "dic",
    ary:dict => "ary",
    opt:try? dict => "opt")   // Optionalはtry?をつける
print( myObj2 )
```



# Decodableの全体をざっくり解説
大まかに見ると、Decodableは以下のコンポーネントで構成されています。

- 既存クラスで拡張される```decode()```関数([Decodable.swift](https://github.com/Anviking/Decodable/blob/master/Sources/Decodable.swift)と[Castable.swift](https://github.com/Anviking/Decodable/blob/master/Sources/Castable.swift))
    - NSDictionary,NSArray Dictionary,Array,String,Int,Double,Bool に、スタティックな```decode()```関数を足しています。
	    - String.decode()やInt.decode()等など。
    - ```decode()```は「AnyObjectを受け取り、その型に変換して返す、変換できなければ例外を投げる」機能を持ちます。
- ```parse()```関数([Parse.swift](https://github.com/Anviking/Decodable/blob/master/Sources/Parse.swift))
    - jsonオブジェクトから値を取得し、その値を引数に```decode```を呼び出す所までを担当します。
- ```=>```オペレータ([Operators.swift](https://github.com/Anviking/Decodable/blob/master/Sources/Operators.swift))
    - 「何の値を返すのか」という種類毎に定義されている。
    - それぞれの種類毎に、どう処理するのかをセットして、```parse()```関数を呼び出しています。    
- 例外([DecodeError.swift](https://github.com/Anviking/Decodable/blob/master/Sources/DecodingError.swift))
    - 「指定されたキーが存在しない」場合と「値が変換できない」場合に例外が飛びます。この例外を表すenumを定義しています。
 
これらが組み合わさり、ざっくりと以下の順番で処理が行われます。
1. ```=>```オペレータで、戻り値の型に応じて、デコードの処理が決定し、その処理とjsonとキーを関数```parse()```に渡します。
2. ```parse()```関数では、キーを使ってjsonの中から値を取り出し、その値をデコードの処理に渡します。もしこの時に値が無い場合は、例外が飛びます。
3. デコードの処理の中で、各型に定義された```decode()```関数が呼び出され、AnyObjectから指定された型に変換されます。もしこの時に型が変換できない場合は、例外が飛びます。

ここまでの大枠は、ソース量も少ないので、ざっくり把握できました。


# それぞれのソースを見ていく
ここから、ソースの書き方を細かく見ていきます。ここからが頭が混乱した所ですし、Swiftでこんな事が出来るんだ！と思った所です。
## [Operators.swift](https://github.com/Anviking/Decodable/blob/master/Sources/Operators.swift)のcatchNull()

ここで定義されているcatchNull()ですが、ぱっと見ても意味がわかりませんでした。多少書き換えて以下に引用します：

```
func catchNull<T>(decodeClosure: (AnyObject throws -> T)) -> (AnyObject throws -> T? ){
    return { json in
        if json is NSNull {
            return nil
        } else {
            return try decodeClosure(json)
        }
    }
}

```
関数catchNull()は、「「関数」を返す関数」のようです。なんだかよくわからないですよね。

```{ xxx in ...}```で、引数を変数xxxに割り当てて、「新しい関数」を定義しています。最後にこの「新しい関数」そのものが```return```で戻り値として返されます。

catchNull()が返す「新しい関数」は、以下の挙動となります。

- 引数がNSNullの場合は、nilを返す
- そうでない場合は、decodeClosureで指定された関数を呼び出し、結果を返す

decodeClosureで指定された関数に「新しくNSNullチェックを追加した」新しい関数を作って返しているようです。NSNullの時にはnilが返るようになるので、戻り値はOptional型(T?)となります。
また、catchNull()関数が呼ばれた時には、引数の関数は実行されず、「新しい関数」を作って返すだけのようです。引数で渡された関数は、新しく作った関数を呼び出した時に、初めて呼ばれます。

どんな戻り値になっているのかわかりやすくするため、くどいぐらいに型を明記してテストしてみました。

```
// String.decodeの型情報をはっきりさせるために、一度変数f1に格納する。
// f1(=String.decode関数)は、「AnyObjectを受け取ってStringを返す」関数
let f1:(AnyObject throws -> String) = String.decode // ここではString.decodeは実行されていない
do{
    let v1_1:String = try f1("123")
    print(v1_1)                         //"123\n"
    let v1_2:String = try f1(NSNull())  // NSNull()が来ると、Stringに変換できないので例外が飛ぶ
    print(v1_2)                         // ここは実行されない。
}catch let error{
    print(error)  // r1_2 の所の例外がここに来る
}


// catchNull()は、「新しい関数を作って返す」関数なので、戻り値が関数になっている
// f2(=catchNull(f1)の戻り値)は、「AnyObjectを受け取ってStringかnil(=Stringのオプショナル型)を返す」関数
let f2:(AnyObject throws -> String?) = catchNull(f1) // ここではf1は実行されていない
do{
    let v2_1:String? = try f2("123")
    print(v2_1)                          //"Optional("123")\n"
    let v2_2:String? = try f2(NSNull())  // NSNull()がf2の中でチェックされ、f1が呼ばれる前にnilを返している
    print(v2_2)                          //"nil\n"
}catch let error{
    print(error)  // この場合例外が発生しない。
}
```



## [Decodable.swift](https://github.com/Anviking/Decodable/blob/master/Sources/Decodable.swift)のdecodeArray()

関数decodeArray()も、「「関数」を返す関数」ですが、書き方がちょっと変わっています。

```
public func decodeArray<T>(elementDecodeClosure: AnyObject throws -> T)(json: AnyObject) throws -> [T] {
    return try NSArray.decode(json).map { try elementDecodeClosure($0) }
}
```

引数部分がカッコ2つセットとなっています。

```
func decodeArray(引数1)(引数2)-> 戻り値
```
この書き方は、「カリー化」と呼ばれるもののようです。[Swiftで関数のカリー化(currying)入門](http://qiita.com/Alex_23drum/items/7a9b0ecd0800483b5ddf)が参考になるかと思います。
decodeArrayの場合、引数1が関数、引数2が値(AnyObject)となっています。
Operator.swiftでは、以下のように呼び出されています。

```
    return try parse(lhs, path: rhs, decode: decodeArray(T.decode))
```
ここでは引数1(=関数)でしか呼び出していないため、戻り値は「引数にAnyObjectを取り値を返す関数」です。decodeArray()も、「「関数」を返す関数」になっています。

このカリー化の書き方ですが、Swift3では廃止されるようです。[Swiftがオープンソース化されたのでSwift2.2/Swift3.0の変更を眺めてみる](http://llcc.hatenablog.com/entry/2015/12/05/151357)
もしもこのカリー化の書き方が無くなった場合には、（自信はないのですが）↓のようになるかな？と思います。catchNull()の書き方に似てますね。

```
public func decodeArray2<T>(elementDecodeClosure: AnyObject throws -> T) -> (AnyObject throws -> [T]) {
    return{ json in
        try NSArray.decode(json).map { try elementDecodeClosure($0) }
    }
}
```

## [Decodable.swift](https://github.com/Anviking/Decodable/blob/master/Sources/Decodable.swift)のdecodeDictionary()

decodeDictionary()は、「「「関数」を返す関数」を返す関数」と、さらにややこしくなっています。

```
public func decodeDictionary<K,V>(keyDecodeClosure: AnyObject throws -> K)(elementDecodeClosure: AnyObject throws -> V)(json: AnyObject) throws -> [K: V] {
    var dict = [K: V]()
    for (key, value) in try NSDictionary.decode(json) {
        try dict[keyDecodeClosure(key)] = elementDecodeClosure(value)
    }
    return dict
}
```

このdecodeDictionaryも、Operator.swiftで使われています。

```
 return try parse(lhs, path: rhs, decode: decodeDictionary(String.decode)(elementDecodeClosure: T.decode))
```
```decodeDictionary(String.decode)```で、新しい関数が作られ、さらに```(elementDecodeClosure: T.decode)```を重ねることで、さらに新しい関数を作って返しているようです。
このdecodeDictionaryも、カリー化の記法をつかわずに書き換えてみます。こちらもこれで正しいのか自信はありません。

```
public func decodeDictionary2<K,V>(keyDecodeClosure:(AnyObject throws -> K)) ->((AnyObject throws -> V) -> ( AnyObject throws -> [K: V])) {
    return { elementDecodeClosure in
        return { json in
            var dict = [K: V]()
            for (key, value) in try NSDictionary.decode(json) {
                try dict[keyDecodeClosure(key)] = elementDecodeClosure(value)
            }
            return dict
        }
    }
}
```

## [Decodable.swift](https://github.com/Anviking/Decodable/blob/master/Sources/Decodable.swift)のArray.decode()
上段で似た名前の関数 decodeArray()がでていますが、これは別物です。
Array.decode()関数は、他のdeode()関数と違って、引数を2つ取る事が出来ます。

```
public static func decode(j: AnyObject, ignoreInvalidObjects: Bool = false) throws -> [Element] {
```
二つ目の引数 ignoreInvalidObjectsは、「変換できないオブジェクトが含まれていた時」の挙動のフラグです。
ignoreInvalidObjectsがfalse(デフォルト)の場合は、T.decode()で変換できない場合は例外が投げられます。
ignoreInvalidObjectsがtrueの場合、変換できなかった要素は無視されます。

```
        if ignoreInvalidObjects {
            return try decodeArray { try? Element.decode($0) }(json: j).flatMap {$0}
        } else {
            return try decodeArray(Element.decode)(json: j)
        }
```
```{try? Element.decode($0)}```によって、新たな関数が生成されています。この関数は「```Element.decode()```の値を返す、もしも例外が発生したら(=変換できなかったら)nilを返す」という挙動になります。この場合、declodeArrayの戻り値の型は、```[Optional(Element)]```となり、nilを含む可能性が出てきます。これを、```.flatMap($0)```で、nilを除去しています。
この流れで、「変換できなかったら無視する」という挙動になっています。

## [Parse.swift](https://github.com/Anviking/Decodable/blob/master/Sources/Parse.swift)のparse()
この関数では、jsonオブジェクトから指定されたパスの値を取り出して、引数decodeで指定された「関数」を実行しています。
処理が多いように見えますが、ほとんどは「jsonオブジェクトから指定されたパスの値を取り出す」処理に費やされています。
コメントを追記した形で、以下に引用します：

```
public func parse<T>(json: AnyObject, path: [String], decode: (AnyObject throws -> T)) throws -> T {
    
    var object = json
    // ここのifのブロックで、「指定されたpathの値の取得」をしている
    if let lastKey = path.last {
        var path = path
        path.removeLast()
        
        var currentDict = try NSDictionary.decode(json)
        var currentPath: [String] = []
        // 関数内で関数を定義している
        func objectForKey(dictionary: NSDictionary, key: String) throws -> AnyObject {
            guard let result = dictionary[NSString(string: key)] else {
                let info = DecodingError.Info(object: dictionary, rootObject: json, path: currentPath)
                throw DecodingError.MissingKey(key: key, info: info)
            }
            return result
        }
		// ここのループで、取得した値が含まれるcurrentDictを取得        
        for key in path {
            currentDict = try NSDictionary.decode(objectForKey(currentDict, key: key))
            currentPath.append(key)
        }
        // 最終的に変換する値(object)を取得
        object = try objectForKey(currentDict, key: lastKey)
    }
    // 取得した値(object)を、指定された関数(decode)に渡している。	
    // catchAndRethrow()は、「例外が発生した時に、引数のjsonとpathを付けて投げ直す」ための関数です。
    return try catchAndRethrow(json, path) { try decode(object) }
    
}
```

catchAndRethrow()関数は、引数を3つ取り値を返す関数です。

```
func catchAndRethrow<T>(json: AnyObject, _ path: [String], block: Void throws -> T) throws -> T {
```

ですが、使う時には「引数2つと関数」を指定しているように見えます。
これを「引数3つ（値2つと関数1つ）」と書くのが関数呼び出し的には普通に感じますが、これ、Array.map等を使う時の書き方に似てるように思います。この辺のルールはまだよく理解できていません。

```
    return try catchAndRethrow(json, path) { try decode(object) }
//    return try catchAndRethrow(json, path, block: { try decode(object) })  // こうとも書ける
// [1,2,3].map{ print($0) } // ←これに似ている気がする
```

# ソースを読んだ感想
Decodableでは、関数型言語の考え方が多数含まれていました。特に「関数を返す関数」で、新しい関数を作るパターンが多数見られます。
この新しい関数を作る、というのは、Objective-Cから来た自分としては、頭が混乱するとともに面白い所でした。
また、「型推論を使って無駄なas?を削っている所」は、型推論に慣れていないので、入力→処理→出力　という流れを逆流しているような、不思議な気分になります。
これらのテクニックが駆使されているので、ソースの重複部分が見当たらず、ものすごくコンパクトになっているんだなとわかりました。


ただ、このようなテクニックを駆使した書き方は、会社のプロジェクトでは正直使いにくいです。新人が発狂してしまいそうです。
自分とメンバーのレベルを上げていかないといけないですね。