Updated for Xcode 14.2
Fetching JSON from the network and using Codable
to convert it into native Swift objects is probably the most common task for any Swift developer, usually followed by displaying that data in a List
or UITableView
depending on whether they are using SwiftUI or UIKit.
Well, using Swift’s concurrency features we can write a small but beautiful extension for URLSession
that makes such work just a single line of code – you just tell iOS what data type to expect and the URL to fetch, and it will do the rest. To add some extra flexibility, we can also provide options to customize decoding strategies for keys, data, and dates, providing sensible defaults for each one to keep our call sites clear for the most common usages.
Here’s how it’s done:
extension URLSession {
func decode<T: Decodable>(
_ type: T.Type = T.self,
from url: URL,
keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys,
dataDecodingStrategy: JSONDecoder.DataDecodingStrategy = .deferredToData,
dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate
) async throws -> T {
let (data, _) = try await data(from: url)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = keyDecodingStrategy
decoder.dataDecodingStrategy = dataDecodingStrategy
decoder.dateDecodingStrategy = dateDecodingStrategy
let decoded = try decoder.decode(T.self, from: data)
return decoded
}
}
That does several things:
URLSession
, so you can go ahead and create your own custom session with a unique configuration if needed.Decodable
protocol – that’s half of Codable
, so if you use Codable
it will work there too.T.self
for the default data type, so if Swift can infer your type then you don’t need to repeat yourself.To use the extension in your own code, first define a type you want to work with, then go ahead and call decode()
in whichever way you need:
extension URLSession {
func decode<T: Decodable>(
_ type: T.Type = T.self,
from url: URL,
keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys,
dataDecodingStrategy: JSONDecoder.DataDecodingStrategy = .deferredToData,
dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate
) async throws -> T {
let (data, _) = try await data(from: url)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = keyDecodingStrategy
decoder.dataDecodingStrategy = dataDecodingStrategy
decoder.dateDecodingStrategy = dateDecodingStrategy
let decoded = try decoder.decode(T.self, from: data)
return decoded
}
}
struct User: Codable {
let id: UUID
let name: String
let age: Int
}
struct Message: Codable {
let id: Int
let user: String
let text: String
}
do {
// Fetch and decode a specific type
let url1 = URL(string: "https://hws.dev/user-24601.json")!
let user = try await URLSession.shared.decode(User.self, from: url1)
print("Downloaded \(user.name)")
// Infer the type because Swift has a type annotation
let url2 = URL(string: "https://hws.dev/inbox.json")!
let messages: [Message] = try await URLSession.shared.decode(from: url2)
print("Downloaded \(messages.count) messages")
// Create a custom URLSession and decode a Double array from that
let config = URLSessionConfiguration.default
config.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
let session = URLSession(configuration: config)
let url3 = URL(string: "https://hws.dev/readings.json")!
let readings = try await session.decode([Double].self, from: url3)
print("Downloaded \(readings.count) readings")
} catch {
print("Download error: \(error.localizedDescription)")
}
Download this as an Xcode project
As you can see, with that small extension in place it becomes trivial to fetch and decode any type of Codable
data with just one line of Swift.
SPONSORED From March 20th to 26th, you can join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer!
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.