🌊

iOS | Alamofire で GET して返ってきた JSON をデコードする

2022/06/22に公開
2

Qiita の API を使って記事を取得してみます。

まずは取得する記事のモデルを作成します。
モデルクラスで定義したプロパティ名と返却される JSON のキーを対応させる必要があるのですが、もし異なる場合はクラス内に CodingKeys という enum を作り、そこでキーを指定してあげます。プロパティ名はキャメルケースだけど JSON のキーはスネークケースの場合などはこの方法でキーを指定します。

【追記】
コメントでもっと簡単な方法を教えていただきました。
スネークケースからキャメルケースの変換だけであれば、JSONDecoder を使うことで CodingKeys の enum はまるっと不要になります(後述)。

struct Article: Codable {
    let title: String
    let commentsCount: Int
    
    // private enum CodingKeys: String, CodingKey {
    //     case title
    //     // プロパティ名と JSON のキーが異なるので指定してあげる
    //     case commentsCount = "comments_count"
    // }
}

次に Alamofire を使って記事を取得します。

responseDecodable(of:) で返却されるモデルクラスを指定しておくと、let articles = response.value とやるだけで JSON から変換されたモデルクラスのインスタンスを取得できます。今回は記事の配列を取得するので Array<Article>.self を指定します。

【追記】
スネークケースからキャメルケースの変換を行う場合、responseDecodable(of:decoder:) に JSONDecoder を指定することで簡単に変換ができます(まったく違う名前に変換したい場合は、引き続き enum CodingKeys で指定する必要があります)。

let url = "https://qiita.com/api/v2/items"
let params = [
    "page": "1",
    "per_page": "10",
]

// スネークケース -> キャメルケースを変換する場合、JSONDecoder を設定する
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

AF.request(url, parameters: params)
    .validate(statusCode: 200..<300)
    .responseDecodable(of: Array<Article>.self, decoder: decoder) { response in
        switch response.result {
        case .success:
	    if let articles = response.value {
	        print("articles: \(articles)")
	    } else {
	        print("articles is nil")
	    }
	case .failure(let error):
	    print(error)
	}
    }

Discussion

Daichi HayashiDaichi Hayashi
    private enum CodingKeys: String, CodingKey {
        case title
	// プロパティ名と JSON のキーが異なるので指定してあげる
        case commentsCount = "comments_count"
    }

この部分ですが、
JSONDecoder.KeyDecodingStrategy | Apple Developer Documentation
これを使って JSONDecoder.KeyDecodingStrategy.convertFromSnakeCase を指定すると今回の場合は enum ごと無くせると思います。

responseDecodable の実装は この辺り を見ると

    public func responseDecodable<T: Decodable>(of type: T.Type = T.self,
                                                queue: DispatchQueue = .main,
                                                dataPreprocessor: DataPreprocessor = DecodableResponseSerializer<T>.defaultDataPreprocessor,
                                                decoder: DataDecoder = JSONDecoder(),
                                                emptyResponseCodes: Set<Int> = DecodableResponseSerializer<T>.defaultEmptyResponseCodes,
                                                emptyRequestMethods: Set<HTTPMethod> = DecodableResponseSerializer<T>.defaultEmptyRequestMethods,
                                                completionHandler: @escaping (AFDataResponse<T>) -> Void) -> Self {

となっていますので、ここに自前の JSONDecoder を渡せば行けそうです。
実際に試したわけではないのでそのまま動くかは自信ないですが、

+ let decoder = JSONDecoder()
+ decoder.keyDecodingStrategy = .convertFromSnakeCase
AF.request(url, parameters: params)
    .validate(statusCode: 200..<300)
-    .responseDecodable(of: Array<Article>.self) { response in
+    .responseDecodable(of: Array<Article>.self, decoder: decoder) { response in

このようなイメージになります。

Yudai WatanabeYudai Watanabe

ご丁寧にコメントありがとうございます!
こちら試したところ、ちゃんと動いているのが確認できたので記事を更新しました🙇‍♂️

モデルクラスが持つプロパティが多くなると CodingKeys が肥大化してしまうなーと思っていたのですが、これでスッキリしそうです!とても助かります!