【Swift】URLSession で JSONを POST して JSON のレスポンスを受け取る

先日、紹介しましたこれ。

👉 httpbin.org hatena-bookmark


https://httpbin.org/anything/{anything}

に対して JSONを POST するとそのまま JSONで返ってきます。

キー名は「json」です。

※ 以下では POST してないので null。


{
  "args": {},
  "data": "",
  "files": {},
  "form": {},
  "headers": {
    "Accept": "application/json",
    "Accept-Encoding": "gzip, deflate, br, zstd",
    "Accept-Language": "ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7",
    "Content-Length": "0",
    "Host": "httpbin.org",
    "Origin": "https://httpbin.org",
    "Priority": "u=1, i",
    "Referer": "https://httpbin.org/",
    "Sec-Ch-Ua": "\"Google Chrome\";v=\"125\", \"Chromium\";v=\"125\", \"Not.A/Brand\";v=\"24\"",
    "Sec-Ch-Ua-Mobile": "?0",
    "Sec-Ch-Ua-Platform": "\"macOS\"",
    "Sec-Fetch-Dest": "empty",
    "Sec-Fetch-Mode": "cors",
    "Sec-Fetch-Site": "same-origin"
  },
  "json": null,  
  "method": "POST",
  "url": "https://httpbin.org/anything/{anything}"
}

練習してみます。

Web-API を使うには必須です。


struct User: Codable {
  var name: String
  var age: Int
}

struct Anything: Codable {
  var json: User
}


let session = URLSession.shared
let url = URL(string: "https://httpbin.org/anything/{anything}")!
let user = User(name: "John", age: 18)

var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = try JSONEncoder().encode(user)

let (data, response) = try await session.data(for: request)
print((response as! HTTPURLResponse).statusCode)
//print(String(data: data, encoding: .utf8)!)

let receivedUser = (try JSONDecoder().decode(Anything.self, from: data)).json

たくさんのキーを持つ JSON がレスポンスで返ってくる場合は、

必要なキーだけを struct のプロパティ(この場合は「json」)

にすれば良いようです。

これで、

「JSON を使った POSTリクエスト」

「JSON のレスポンスからクラスオブジェクトを取得」

ができますね!

 

🙆🏻‍♂️ 参考

👉 【Swift】 JSONEncoder / JSONDecorder の基本的な使い方 hatena-bookmark


【Swift】 JSONEncoder / JSONDecorder の基本的な使い方

Apple 公式リファレンスから。サンプルも抜粋。


func encode<T>(_ value: T) throws -> Data where T : Encodable


func decode<T>(
    _ type: T.Type,
    from data: Data
) throws -> T where T : Decodable

👉 encode(_:) | Apple Developer Documentation hatena-bookmark
👉 decode(_:from:) | Apple Developer Documentation hatena-bookmark


struct GroceryProduct: Codable {
  var name: String
  var points: Int
  var description: String?
}

let pear = GroceryProduct(name: "Pear", points: 250, description: "A ripe pear.")

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted

let data = try encoder.encode(pear)
print(String(data: data, encoding: .utf8)!)

/*
{
  "name" : "Pear",
  "points" : 250,
  "description" : "A ripe pear."
}
*/


struct GroceryProduct: Codable {
  var name: String
  var points: Int
  var description: String?
}

let json = """
{
    "name": "Durian",
    "points": 600,
    "description": "A fruit with a distinctive scent."
}
""".data(using: .utf8)!

let decoder = JSONDecoder()
let product = try decoder.decode(GroceryProduct.self, from: json)

print(product.name) 

// Prints "Durian"

👉 JSONEncoder | Apple Developer Documentation hatena-bookmark
👉 JSONDecoder | Apple Developer Documentation hatena-bookmark

Codable というのは、Encodable + Decodable のこと。


typealias Codable = Decodable & Encodable

👉 Codable | Apple Developer Documentation hatena-bookmark

 

🧑🏻‍💻 まとめ

まとめると以下のイメージ。

Data と String の相互変換は以下。


さらに、キー名のカスタムをしたい場合は以下から。

👉 Encoding and Decoding Custom Types | Apple Developer Documentation hatena-bookmark



【Swift】URLSession メソッドの使い分け

以前、通信部分の書き方を調べていたときに、

👉 【Swift】URLSession.shared.dataTask() をうまく使いこなせない hatena-bookmark

古い API の公での dis がワロエタ。

👉 Use async/await with URLSession - WWDC21 - Videos - Apple Developer hatena-bookmark

先人を敬えよ。

でも分かりやすくなって非常に良い。

 

🧑🏻‍💻 新しいAPIの使い分け

新しい API として以下が上げられている。


URLSession.shared.data() 
URLSession.shared.download()
URLSession.shared.upload()
URLSession.shared.bytes()

ドキュメントを見ながら、引数と戻り値がわかりやすように並べてみる。


func data(
    for request: URLRequest,
    delegate: (any URLSessionTaskDelegate)? = nil
) async throws -> (Data, URLResponse)


func data(
    from url: URL,
    delegate: (any URLSessionTaskDelegate)? = nil
) async throws -> (Data, URLResponse)


func download(
    for request: URLRequest,
    delegate: (any URLSessionTaskDelegate)? = nil
) async throws -> (URL, URLResponse)


func download(
    from url: URL,
    delegate: (any URLSessionTaskDelegate)? = nil
) async throws -> (URL, URLResponse)


func download(
    resumeFrom resumeData: Data,
    delegate: (any URLSessionTaskDelegate)? = nil
) async throws -> (URL, URLResponse)


func upload(
    for request: URLRequest,
    from bodyData: Data,
    delegate: (any URLSessionTaskDelegate)? = nil
) async throws -> (Data, URLResponse)


func upload(
    for request: URLRequest,
    fromFile fileURL: URL,
    delegate: (any URLSessionTaskDelegate)? = nil
) async throws -> (Data, URLResponse)


func bytes(
    for request: URLRequest,
    delegate: (any URLSessionTaskDelegate)? = nil
) async throws -> (URLSession.AsyncBytes, URLResponse)


func bytes(
    from url: URL,
    delegate: (any URLSessionTaskDelegate)? = nil
) async throws -> (URLSession.AsyncBytes, URLResponse)

なんとなく見えてきます。

 

🧑🏻‍💻 使い分けの基準

使い分けの基準としては、

「受け取りたいデータ形式」

ですね。

受け取ってからの利用するデータ形式が、


テキスト → .data()

ファイル → .download()

バイトバッファー → .bytes()

となります。

あとは、その他として


ファイルを送りたい → .upload()

となります。

リクエスト後のレスポンスは、すべてにある戻り値 URLResponse を使って


urlresponse as? HTTPURLResponse

のようにダウンキャストして、レスポンスコードなどを確認します。

 

🧑🏻‍💻 URL か URLRequest か

それぞれのメソッドに引数には、URL か URLRequest があります。

これらの使い分けとしては、

リクエストメソッド・ヘッダーのカスタムが


必要でない → URL

必要 → URLRequest

というかんじでしょうか。

リクエストヘッダーのカスタムは以下のような感じで書けるようです。


var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer 123456ABC", forHTTPHeaderField: "Authorization")
request.httpBody = try! JSONEncoder().encode(User(name: "John", age: 15))

let (data, response) = try await session.data(for: request)
print((response as! HTTPURLResponse).statusCode)
print(String(data: data, encoding: .utf8)!)

 

🧑🏻‍💻 まとめ

「リクエストヘッダーを常に意識しながら、JSONを受け取る。」

という感じの以下が最も使える基本的なメソッドとなるように思います。


func data(
    for request: URLRequest,
    delegate: (any URLSessionTaskDelegate)? = nil
) async throws -> (Data, URLResponse)

このメソッドだけで、

リクエストメソッドとヘッダー次第で、

ほとんどできるのではないかと思ったりもする。

👉 【Swift】ファイルやディレクトリのパスが長すぎていやだ – URL.shortPath() hatena-bookmark

最後に、便利な無料確認サービスをどうぞ。


👉 httpbin.org hatena-bookmark