SwiftyJsonって便利


こんにちは。
久々に更新ですが、そろそろswiftを触り始めて２ヶ月、プログラミングを始めて4ヶ月くらいが経ちました。
今回はAPI関連の有名ライブラリの一つである、SwiftyJsonに感動したのでまとめてみます。

実装したいこととしては
APIから取得したjsonを引数にしてmodelオブジェクトを作成していき、こうしてできた複数のオブジェクトを子要素に持つarrayを返すような関数を作成することです。


```
# Apiから取得するData

[
  {
    "title" : "hoge",
    "url" : "https:hogehogehogehogehoge",
    "code" : "hoge_result"
  },
  {
    "title" : "fuga",
    "url" : "https:fugafugafugafugafugafuga",
    "code" : "fuga_result"
  },
  {
    "title" : "poyo",
    "url" : "https:poyopoyopoyopoyopoyo",
    "code" : "poyo_result"
  }
]

```


```ResultModel.swift
import RealmSwift
import SwiftyJSON

final class ResultModel: Object, ResponseCollectionSerializable {
    dynamic var title: String = ""
    dynamic var url: String = ""
    dynamic var code: String = ""
    
    convenience required init(object: JSON) {
        self.init()
        self.title = object["title"].stringValue
        self.url = object["url"].stringValue
        self.code = object["code"].stringValue
    }

static func collection(object: JSON) -> [ResultModel] {
	let json: [JSON] = object.array!
	var array = [ResultModel]()
	for item in json {
 		array.append(ResultModel(object: item))
    }
   	return array
}
```

```APIResponseObjectSerializable.swift
import Alamofire
import SwiftyJSON

public protocol ResponseObjectSerializable {
    init?(object: JSON)
}

public protocol ResponseCollectionSerializable {
    static func collection(object: JSON) -> [Self]
}
```


ResultModelの`static func collection`の部分を
>
object.arrayValue.map { TestResultModel(object: $0) }
に変えましょう

とレビューされこんな短く書けるのかと感動しました。

#SwiftyJson

まず、SwiftyJsonとはどういうライブラリかというと、JSONファイルをパースし、Swiftで取り扱うためのライブラリです。
Cocoa TouchフレームワークにもJSONを扱うフレームワークは含まれているが、よりシンプルにJSONを取り扱うことができるみたいです。


##とりあえずReadme.mdを読んでみる

SwiftyJsonの[`ReadMe.md`](https://github.com/SwiftyJSON/SwiftyJSON)を読んでみると基本的な使い方としては以下のようになります。

```
let json = JSON(data: dataFromNetworking)
if let userName = json[0]["user"]["name"].string {
  //Now you got your value
}
```

jsonで書かれた配列をdouble型やString型で書くことができたり、pathを指定する配列を定義すればよりシンプルに書くことができたりと便利そうですね。

```Subscript
//Getting a double from a JSON Array
let name = json[0].double

//Getting a string from a JSON Dictionary
let name = json["name"].stringValue

//Getting a string using a path to the element
let path = [1,"list",2,"name"]
let name = json[path].string
//Just the same
let name = json[1]["list"][2]["name"].string
//Alternatively
let name = json[1,"list",2,"name"].string

//With a hard way
let name = json[].string

//With a custom way
let keys:[SubscriptType] = [1,"list",2,"name"]
let name = json[keys].string
```

またDictionary型のjsonの場合はfor文で回して処理することもできます。しかし、最初の要素はString型でなければいけないようです。

```Loop
//If json is .Dictionary
for (key,subJson):(String, JSON) in json {
   //Do something you want
}

//If json is .Array
//The `index` is 0..<json.count's string value
for (index,subJson):(String, JSON) in json {
    //Do something you want
}

```

では次にgetterを見ていきましょう。
Optional getterとNon-optional getterの2種類書かれています。
nilを許すか許さないかということの違いでしょうか？
optionalの方はnilだったらerrorを吐くようにしていますね。

```
# Optional getter

//NSNumber
if let id = json["user"]["favourites_count"].number {
   //Do something you want
} else {
   //Print the error
   print(json["user"]["favourites_count"].error)
}


//String
if let id = json["user"]["name"].string {
   //Do something you want
} else {
   //Print the error
   print(json["user"]["name"])
}


//Bool
if let id = json["user"]["is_translator"].bool {
   //Do something you want
} else {
   //Print the error
   print(json["user"]["is_translator"])
}


//Int
if let id = json["user"]["id"].int {
   //Do something you want
} else {
   //Print the error
   print(json["user"]["id"])
}
```

Non-optionalの方はnilを許していないのでnilは入っていないという前提でしか使わないということですね。違う型のものが入っている時も返すので注意ですね。（そんなことはなさそうですが）
やっていることとしてはpathで指定したobjectのvalueをキャストしているのでしょうか？

```
# Non-optional getter

//If not a Number or nil, return 0
let id: Int = json["id"].intValue

//If not a String or nil, return ""
let name: String = json["name"].stringValue

//If not a Array or nil, return []
let list: Array<JSON> = json["list"].arrayValue

//If not a Dictionary or nil, return [:]
let user: Dictionary<String, JSON> = json["user"].dictionaryValue
```


Setterも見てみましたが、pathと型を指定してvalueを宣言する感じですね。

```
# setter

json["name"] = JSON("new-name")
json[0] = JSON(1)

json["id"].int =  1234567890
json["coordinate"].double =  8766.766
json["name"].string =  "Jack"
json.arrayObject = [1,2,3,4]
json.dictionary = ["name":"Jack", "age":25]
```

Raw objectはこちら。

```
# RawObject

let jsonObject: AnyObject = json.object

if let jsonObject: AnyObject = json.rawValue

//convert the JSON to raw NSData
if let data = json.rawData() {
    //Do something you want
}

//convert the JSON to a raw String
if let string = json.rawString() {
    //Do something you want
}

```

ん〜。
RawObjectとNon-optional getterの違いがいまいちわかりませんでした。ので、`SwiftyJSON.swift`のcodeを見てみました。
前者はjsonオブジェクトを違う型のobjectへと変換する(型がString型などに定まる)ことで、後者はjsonオブジェクト内の指定したpathにあるNon-optional型のobjectのプロパティのvalueを取得するてイメージですかね？

```SwiftyJSON.swift
// MARK: - Raw

extension JSON: Swift.RawRepresentable {

    public init?(rawValue: AnyObject) {
        if JSON(rawValue).type == .Unknown {
            return nil
        } else {
            self.init(rawValue)
        }
    }

    public var rawValue: AnyObject {
        return self.object
    }

    public func rawData(options opt: NSJSONWritingOptions = NSJSONWritingOptions(rawValue: 0)) throws -> NSData {
        guard NSJSONSerialization.isValidJSONObject(self.object) else {
            throw NSError(domain: ErrorDomain, code: ErrorInvalidJSON, userInfo: [NSLocalizedDescriptionKey: "JSON is invalid"])
        }

        return try NSJSONSerialization.dataWithJSONObject(self.object, options: opt)
    }

    public func rawString(encoding: UInt = NSUTF8StringEncoding, options opt: NSJSONWritingOptions = .PrettyPrinted) -> String? {
        switch self.type {
        case .Array, .Dictionary:
            do {
                let data = try self.rawData(options: opt)
                return NSString(data: data, encoding: encoding) as? String
            } catch _ {
                return nil
            }
        case .String:
            return self.rawString
        case .Number:
            return self.rawNumber.stringValue
        case .Bool:
            return self.rawNumber.boolValue.description
        case .Null:
            return "null"
        default:
            return nil
        }
    }
}

```

Alamofireと連携して書くと以下のようになります。

```
# WorkwithAlamofire

Alamofire.request(.GET, url).validate().responseJSON { response in
    switch response.result {
    case .Success:
        if let value = response.result.value {
          let json = JSON(value)
          print("JSON: \(json)")
        }
    case .Failure(let error):
        print(error)
    }
}
```


以上が`Readme.md`のまとめでした。
`Readme.md`を踏まえるとarrayvalueを使うと良さそうな匂いがします。


#Swiftのmap
こちらの記事が参考になりました。
[Swift 2.0の新しいflatMapが便利過ぎる](http://qiita.com/koher/items/6a95cb8edb04fb81f787)
[ArrayとOptionalのmapは同じです](http://qiita.com/koher/items/68585cff691d87124077)

```map
var array:Array<Int> = [2,3,5]
array = array.map { $0 * 2 }  // [4,6,10]
```


#本題
では本題へ戻ります。

実装したいことは、
「APIから取得したjsonオブジェクトを引数にしてmodelオブジェクトを作成していき、こうしてできた複数のオブジェクトを子要素に持つarrayを返すような関数を作成すること」
でした。

以上を踏まえると

```
func collection(object: JSON) -> [ResultModel]{
	object.arrayValue.map { ResultModel(object: $0) }
}
```
こう書けるのは自明ですね。苦笑

APIの構造を見て、直感的に書けるのは嬉しいですね。