Edited at

SwiftyJsonって便利

More than 3 years have passed since last update.

こんにちは。

久々に更新ですが、そろそろswiftを触り始めて2ヶ月、プログラミングを始めて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を読んでみると基本的な使い方としては以下のようになります。

let json = JSON(data: dataFromNetworking)

if let userName = json[0]["user"]["name"].string {
//Now you got your value
}

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

//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型でなければいけないようです。

//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はこちら。

# RawObject

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が便利過ぎる

ArrayとOptionalの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の構造を見て、直感的に書けるのは嬉しいですね。