SwiftUI is supposed to simplify things- I am bit frustrated as I have been working on the URLSession+JSONDecoder for weeks, I really need some help!
I have a function to load JSON data from a file in Swift and it works as expected. I copy/pasted the function and updated it to get the data via an API, however I receive a compile time error: "Unexpected non-void return value in void function". Is my approach wrong to use a function for JSON over the web?
JSON response:
{
"T":"CSU",
"v":468303,
"vw":1.2838,
"o":1.31,
"c":1.24,
"h":1.38,
"l":1.2001,
"t":1607374800000,
"n":994
}
struct Root2: Codable {
var T: String
var v: Double
var vw: Double
var o: String
var c: String
var h: Double
var l: Double
var t: Double
}
This file-based function works as expected:
let symbolData: [Root2] = load("symbolData.json")
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
For the web version, I receive compile time error: "Unexpected non-void return value in void function".
Line: return try decoder.decode(T.self, from: data)
func loadURL<T: Decodable>() -> T {
guard let url = URL(string: """)
else {
fatalError("Invalid URL in main bundle.")
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
do {
if let data = data {
let stringData = String(decoding: data, as: UTF8.self)
print("1 Fetched: \(url)")
print("2 Response: \(stringData)")
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
}
}
catch {
fatalError("Couldn't parse as :\n\(error)")
}
}.resume()
}
Working version after Leo's help!
class Manager: ObservableObject {
#Published var symbols: [Symbol] = []
func loadURL<T: Decodable>(using decoder: JSONDecoder = .msSince1970, completion: #escaping ((T?, Error?) -> Void)) {
let url = URL(string: """)!
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print("ops")
completion(nil, error)
return
}
print("1 Fetched: \(url)")
print("2 Response:", String(data: data, encoding: .utf8) ?? "")
_ = Data("""
[
{
"open": {
"price": 124.02,
"time": 1657105851499
},
"close": {
"price": 124.96,
"time": 1618647822184
},
"high": 124.64,
"low": 124.65,
"volume": 75665274,
"symbol": "AAPL"
}
]
""".utf8)
do {
completion(try decoder.decode(T.self, from: data), nil)
//completion(try decoder.decode(T.self, from: tempDataForTesting), nil)
} catch {
completion(nil, error)
}
}.resume()
}
}
extension JSONDecoder {
static let msSince1970: JSONDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .millisecondsSince1970
return decoder
}()
}
You can't wait for an asynchronous task to finish and return the result. What you need is a completion handler. You would need also to explicitly set the resulting type if you don't pass the resulting type to your decode method and you need to call resume to start your url session data task:
import SwiftUI
struct ContentView: View {
#ObservedObject var manager = Manager()
#State var string: String = "Hello, world!"
var body: some View {
Text(manager.symbol)
.padding()
.onAppear {
manager.load(symbol: manager.symbol) { (symbols: [Symbol]?, error: Error?) in
guard let symbols = symbols else {
print("error:", error ?? "")
string = "JSON could not be parsed"
return
}
for symbol in symbols {
print(symbol.open.price)
print(symbol.open.time)
print(symbol.close.price)
print(symbol.close.time)
print(symbol.high)
print(symbol.low)
print(symbol.volume)
print(symbol.symbol)
DispatchQueue.main.async {
manager.symbols = symbols
}
}
string = "JSON was successufly parsed"
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
class Manager: ObservableObject {
#Published var symbols: [Symbol] = []
#Published var symbol: String = "IBM"
func load<T: Decodable>(symbol: String, using decoder: JSONDecoder = .msSince1970, completion: #escaping ((T?, Error?) -> Void)) {
guard let url = URLComponents(symbol: symbol).url else {
completion(nil, URL.Error.invalidURL)
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print("ops")
completion(nil, error)
return
}
print("1 Fetched: \(url)")
print("2 Symbol: \(symbol)")
print("3 Response:", String(data: data, encoding: .utf8) ?? "")
do {
completion(try decoder.decode(T.self, from: data), nil)
} catch {
completion(nil, error)
}
}.resume()
}
}
struct Symbol: Codable {
let open, close: Price
let high, low: Double
let volume: Int
let symbol: String
}
struct Price: Codable {
let price: Double
let time: Date
}
extension JSONDecoder {
static let msSince1970: JSONDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .millisecondsSince1970
return decoder
}()
}
extension URLComponents {
init(scheme: String = "https",
host: String = "sandbox.iexapis.com",
path: String = "/stable/stock/market/ohlc",
symbol: String,
token: String = "YOUR_API_TOKEN") {
self.init()
self.scheme = scheme
self.host = host
self.path = path
self.queryItems = [URLQueryItem(name: "symbols", value: symbol),
URLQueryItem(name: "token", value: token)]
}
}
extension URL {
enum Error: String, Swift.Error {
case invalidURL = "Invalid URL"
}
}
This will print
1 Fetched: https://sandbox.iexapis.com/stable/stock/market/ohlc?symbols=IBM&token=YOUR_API_TOKEN
2 Symbol: IBM
3 Response: [{"open":{"price":128.9,"time":1636600302693},"close":{"price":131.44,"time":1662259300134},"high":132.517,"low":130.074,"volume":3403359,"symbol":"IBM"}]
128.9
2021-11-11 03:11:42 +0000
131.44
2022-09-04 02:41:40 +0000
132.517
130.074
3403359
IBM
Related
So, Xcode 13.1, iOS 15.0, I am stuck with this JSON serialization for a rather important project which has to be delivered yesterday(!) as usual, and instead crashes.
This is the piece of code I'm dealing with:
var jsonData: Data
do {
jsonData = try JSONSerialization.data(withJSONObject: roomBookings, options: [])
} catch {
print("error: ", error)
return false
}
This is the object I am trying to JSON serialize:
Rooms.RoomBookings(
bookings: [Rooms.RoomBooking(
bookingID: "23EB86CB-A918-47D4-ADDB-346DBB4E3471",
roomID: "BgX86SbN0UifijkwU8HZ",
isAllDay: false,
startTimestamp: 1636440856861,
endTimestamp: 1636444456861,
user: "trialUser")
])
This is the error I keep getting, which makes the app crash (of course we can avoid the actual crash checking with isValidJSONObject, but that's not the point here).
Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '*** +[NSJSONSerialization dataWithJSONObject:options:error:]:
Invalid top-level type in JSON write'
Here below is the model, The error is focused on the top-level type, what's wrong with that? It's just an Array of RoooBooking instances, conformed with Codable, so what?
Someone knows what am I doing wrong? That would be greatly appreciated!
// MARK: - RoomBookings
struct RoomBookings: Codable {
var bookings: [RoomBooking]
enum CodingKeys: String, CodingKey {
case bookings = "bookings"
}
}
// MARK: RoomBookings convenience initializers and mutators
extension RoomBookings {
init(data: Data) throws {
self = try newJSONDecoder().decode(RoomBookings.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func with(
bookings: [RoomBooking]? = nil
) -> RoomBookings {
return RoomBookings(
bookings: bookings ?? self.bookings
)
}
func jsonData() throws -> Data {
return try newJSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
// MARK: - RoomBooking
struct RoomBooking: Codable {
var bookingID: String
var roomID: String
var isAllDay: Bool
var startTimestamp: Int
var endTimestamp: Int
var user: String
enum CodingKeys: String, CodingKey {
case bookingID = "bookingId"
case roomID = "roomId"
case isAllDay = "isAllDay"
case startTimestamp = "startTimestamp"
case endTimestamp = "endTimestamp"
case user = "user"
}
}
// MARK: RoomBooking convenience initializers and mutators
extension RoomBooking {
init(data: Data) throws {
self = try newJSONDecoder().decode(RoomBooking.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func with(
bookingID: String? = nil,
roomID: String? = nil,
isAllDay: Bool? = nil,
startTimestamp: Int? = nil,
endTimestamp: Int? = nil,
user: String? = nil
) -> RoomBooking {
return RoomBooking(
bookingID: bookingID ?? self.bookingID,
roomID: roomID ?? self.roomID,
isAllDay: isAllDay ?? self.isAllDay,
startTimestamp: startTimestamp ?? self.startTimestamp,
endTimestamp: endTimestamp ?? self.endTimestamp,
user: user ?? self.user
)
}
func jsonData() throws -> Data {
return try newJSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
// MARK: - Helper functions for creating encoders and decoders
func newJSONDecoder() -> JSONDecoder {
let decoder = JSONDecoder()
if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
decoder.dateDecodingStrategy = .iso8601
}
return decoder
}
func newJSONEncoder() -> JSONEncoder {
let encoder = JSONEncoder()
if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
encoder.dateEncodingStrategy = .iso8601
}
return encoder
}
Try
var jsonData: Data
do {
jsonData = try JSONEncoder().encode(roomBookings)
print(String(data: data, encoding: .utf8)!) //to check the actual O/P am adding it here, remove it from your code
} catch {
print("error: ", error)
return false
}
O/P:
{ "bookings": [{ "endTimestamp": 1, "roomId": "1", "user":
"abcd", "isAllDay": true, "bookingId": "1", "startTimestamp": 1
}] }
Being a Swift newbie I am trying to download and parse a CSV file using the code:
URLSession.shared.dataTaskPublisher(for: url)
.tryMap(handleOutput)
.sink { completion in
} receiveValue: { csvWords in
let lines = csvWords.split(separator: "\n")
for line in lines {
let columns = line.split(separator: ",")
for column in columns {
print("column: \(column)")
}
}
however I get the syntax error:
Cannot convert value of type 'String' to expected argument type 'Data.Element' (aka 'UInt8')
Please help me to understand, what is happening.
What value is returned by the receiveValue, isn't it the remote file content as string?
UPDATE:
Here is the missing method
func handleOutput(output: URLSession.DataTaskPublisher.Output) throws -> Data {
guard
// as? means "this might be nil"
let response = output.response as? HTTPURLResponse,
response.statusCode >= 200,
response.statusCode < 300
else {
throw URLError(.badServerResponse)
}
return output.data
}
Judging from your error message, handleOutput would appear to be publishing Data. If you want to call string functions, you would map that to String, e.g.,
var cancellable: AnyCancellable?
func foo(_ url: URL) {
cancellable = URLSession.shared.dataTaskPublisher(for: url)
.tryMap(handleOutput)
.compactMap { String(data: $0, encoding: .utf8) }
.sink { completion in
...
} receiveValue: { string in
...
}
}
Or, if you want, you could throw an error if it can't be converted to a string:
var cancellable: AnyCancellable?
func foo(_ url: URL) {
cancellable = URLSession.shared.dataTaskPublisher(for: url)
.tryMap(handleOutput)
.tryMap { data -> String in
guard let string = String(data: data, encoding: .utf8) else {
throw URLError(.badServerResponse)
}
return string
}
.sink { completion in
...
} receiveValue: { string in
...
}
}
In your revised question, you shared handleOutput. You could simply change that to produce your String for you:
func handleOutput(output: URLSession.DataTaskPublisher.Output) throws -> String {
guard
let response = output.response as? HTTPURLResponse,
200 ..< 300 ~= response.statusCode,
let string = String(data: output.data, encoding: .utf8)
else {
throw URLError(.badServerResponse)
}
return string
}
And then you don't need that extra compactMap/tryMap:
func foo(_ url: URL) {
cancellable = URLSession.shared.dataTaskPublisher(for: url)
.tryMap(handleOutput)
.sink { completion in
...
} receiveValue: { string in
...
}
}
I'm trying to build a engine that will take a few pieces of data and parse them through a wikidata query...there are two url session tasks here is my code, but for some reason I can't get the functions to perform the networking to execute...what am I missing?
struct BDayWikiDataManager {
func fetchBDayDataFromWikipedia(numMonth: String, numDay: String) -> (String, String, String) {
var personLabelCode = "no value"
var occupationLabelCode = "no value"
var dob = "no value"
var personName = "no value"
var occupationName = "no value"
let firstURL = "https://query.wikidata.org/sparql?query=SELECT%20distinct%20%3Fperson%20%3FpersonLabel%20%3Fdob%20%3Foccupation%20%3FoccupationLabel%20%3Fawards%20%3FawardsLabel%0AWHERE%0A{%0A%20%20%3Fperson%20wdt%3AP31%20wd%3AQ5%3B%0A%20%20%20%20%20%20%20%20%20%20wdt%3AP27%20wd%3AQ30%3B%0A%20%20%20%20%20%20%20%20%20%20wdt%3AP569%20%3Fdob%3B%0A%20%20%20%20%20%20%20%20%20%20wdt%3AP166%20%3Fawards%3B%0A%20%20%20%20%20%20%20%20%20%20wdt%3AP106%20%3Foccupation.%0A%20%20OPTIONAL%20{%3Fperson%20wdt%3AP166%20%3Fawards}%0A%20%20FILTER(DAY(%3Fdob)%20%3D%20\(numDay)).%0A%20%20FILTER(MONTH(%3Fdob)%20%3D%20\(numMonth)).%0A%20%20FILTER(YEAR(%3Fdob)%20%3E%3D%201940).%0A%20%20FILTER%20(%3Foccupation%20in%20(%20wd%3AQ33999%2C%20wd%3AQ639669%2C%20wd%3AQ2066131%2C%20wd%3AQ947873%2C%20wd%3AQ15265344%20)%20)%0A%20%20SERVICE%20wikibase%3Alabel%20{%20bd%3AserviceParam%20wikibase%3Alanguage%20%22[AUTO_LANGUAGE]%22.%20}%20%0A}%0ALIMIT%2050&format=json"
performRequestFirstURL(with: firstURL) { (data, error) in
if error != nil {
print(error as Any)
}
personLabelCode = data!.personLabel
print(personLabelCode)
occupationLabelCode = data!.occupationLabel
dob = data!.dob
}
print(personLabelCode)
let secondUrl = "https://www.wikidata.org/w/api.php?action=wbgetentities&ids=\(personLabelCode)|\(occupationLabelCode)&format=json&props=labels&languages=en"
let _ = performRequestSecondURL(with: secondUrl, personLabel: personLabelCode, occupationLabel: occupationLabelCode) { (data, error) in
if error != nil {
fatalError("performRequestSecondURL failed, error = \(String(describing: error))")
}
personName = data!.personName
occupationName = data!.occupationName
}
return (personName, occupationName, dob)
}
func performRequestFirstURL(with urlString: String, userCompletionHandler: #escaping (FirstURLData?, Error?) -> Void) {
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url, completionHandler: { data, response, error in
if let data = data {
let jsonDecoder = JSONDecoder()
do {
let parsedJSON = try jsonDecoder.decode(FirstURLIncomingData.self, from: data)
print(parsedJSON)
let numberOfBindings = parsedJSON.results.bindings.count
let randomBindingNumber = Int.random(in: 0...(numberOfBindings - 1))
let dob = parsedJSON.results.bindings[randomBindingNumber].dob.value
let personLabel = parsedJSON.results.bindings[randomBindingNumber].personLabel.value
let occupationLabel = parsedJSON.results.bindings[randomBindingNumber].occupationLabel.value
let finalData = FirstURLData(dob: dob, personLabel: personLabel, occupationLabel: occupationLabel)
userCompletionHandler(finalData, nil)
} catch {
print(error)
userCompletionHandler(nil,error)
}
}
})
.resume()
}
}
func performRequestSecondURL(with urlString: String, personLabel: String, occupationLabel: String, userCompletionHandler: #escaping (SecondURLData?, Error?) -> Void) {
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url, completionHandler: { data, response, error in
if let data = data {
let jsonDecoder = JSONDecoder()
do {
let parsedJSON = try jsonDecoder.decode(SecondURLIncomingData.self, from: data)
print(parsedJSON)
let name = parsedJSON.entities[personLabel]?.labels.en.value
let occupation = parsedJSON.entities[occupationLabel]?.labels.en.value
let finalData = SecondURLData(personName: name!, occupationName: occupation!)
userCompletionHandler(finalData, nil)
} catch {
print(error)
userCompletionHandler(nil,error)
}
}
})
.resume()
}
}
}
//MARK: - incoming data model - first url
struct FirstURLIncomingData: Codable {
let results: Results
}
struct Results: Codable {
let bindings: [Bindings]
}
struct Bindings: Codable {
let dob: DOB
let personLabel: PersonLabel
let occupationLabel: OccupationLabel
}
struct DOB: Codable {
let value: String
}
struct PersonLabel: Codable {
let value: String
}
struct OccupationLabel: Codable {
let value: String
}
//MARK: - incoming data model - second url
struct SecondURLIncomingData: Decodable {
let entities: [String : Locator]
}
struct Locator: Decodable {
let labels: Labels
}
struct Labels: Decodable {
let en: EN
}
struct EN: Decodable {
let value: String
}
//MARK: - model of data for both urls
struct FirstURLData: Decodable {
let dob: String
let personLabel: String
let occupationLabel: String
}
struct SecondURLData {
let personName: String
let occupationName: String
}
let manager = BDayWikiDataManager()
let data = manager.fetchBDayDataFromWikipedia(numMonth: "8", numDay: "13")
print(data) //prints "no data" for everything, which means the values didnt update
Worth noting: when I go to the url's manually, I get json responses in my browser, so I know the url's are correct...
I'm trying to retrieve data from an API url
This is what the implementation guide reads, so the URL should match this format:
The request for information in JSON format is submitted as a GET
operation to the endpoint:
http://digit-eyes.com/gtin/v2_0/?upc_code=x&app_key=x&signature=x&language=x&field_names=x
This is my function that fetches the data from the JSON and decodes it from JSON.
I've replaced the signature and API Key with x.
Signature is generated by combining the app_key and the barcode forming a hashed value.
func loadData() {
guard let url = URL(string: "https://www.digit-eyes.com/gtin/v2_0/?upcCode=5901905880016&language=en&app_key=x&signature=x&language=en&field_names=description,brand,ingredients,image,upc_code") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, responce, error in
if let data = data {
if let decodedRepsonce = try? JSONDecoder().decode(Response.self, from: data) {
DispatchQueue.main.async{
self.results = decodedRepsonce.results
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")") //This is the error I get
}.resume()
}
Here's what I get when I paste the URL into safari
I've tested the URL with: "https://itunes.apple.com/search?term=radiohead&entity=song" and it works. A noticeable difference is that this link downloads a JSON file, my URL doesn't.
I store the JSON into an array Results:
struct Result: Codable {
var description: String
var brand: String
var ingredients: String
var image: String
var upc_code: Int
}
Which is then displayed in the body:
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
self.indicator.padding()
List(self.results, id: \.upc_code) { item in
VStack(alignment: .leading) {
Text(item.brand)
.font(.headline)
Text(item.description)
}
}
}
EDIT
Dealing with nulls from the JSON data
To call loadData, I have an .onAppear on a VStack in the body.
.onAppear {
//let signiture = self.scannedCode.barcode.hashedValue("Ls75O8z1q9Ep9Kz0")
self.loadData(url: "https://www.digit-eyes.com/gtin/v2_0/?upcCode=5901905880016&language=en&app_key=/9nOS+obsRF5&signature=DiKl4lURenoNe53I0a/i3kiAkQQ=&language=en&field_names=description,ingredients,brand,image") { error, result in
if let err = error {
print(err)
}
}
}
}
This is in a struct outside of the body
func loadData(url: String, completion: #escaping (Error?, Result?) -> Void) {
if let url = URL(string: url) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {return}
do {
let decoder = JSONDecoder()
let result: Result = try decoder.decode(Result.self, from: data)
completion(nil, result)
}
catch let e {
print(e)
completion(e, nil)
}
}
task.resume()
}
}
}
I'm now getting:
valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "brand", intValue: nil)], debugDescription: "Expected String value but found null instead.", underlyingError: nil))
In the JSON object, the brand name isn't always found, so it's sometimes null. I don't know how I can resume the decoder if a null is found.
Try this code to call in the body:
func loadData(url: String, completion: #escaping (Error?, Result?) -> Void) {
if let url = URL(string: url) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {return}
do {
let decoder = JSONDecoder()
let result: Result = try decoder.decode(Result.self, from: data)
completion(nil, result)
}
catch let e {
print(e)
completion(e, nil)
}
}
task.resume()
}
}
loadData(url: "https://google.com") { error, result in
if let err = error {
print(err)
}
}
Try to modify the struct as follows and add the other variables. I also noticed that upc_code is a String.
struct Result: Codable {
var description: String?
var brand: String?
var ingredients: String?
var image: String?
var upc_code: String?
var return_message: String?
var return_code: String?
}
I am trying to parse this JSON using Codable:
{
"users": [
{
"id": 1,
"name": "Allen Carslake",
"userName": "acarslake0",
"profileImage": "https://source.unsplash.com/random/400x400",
"createdDate": "2019-07-08T00:00:00.000+0000"
},
{
"id": 2,
"name": "Revkah Antuk",
"userName": "rantuk1",
"profileImage": "https://source.unsplash.com/random/400x400",
"createdDate": "2019-07-05T00:00:00.000+0000"
},
{
"id": 3,
"name": "Mirna Saffrin",
"userName": "msaffrin2",
"profileImage": "https://source.unsplash.com/random/400x400",
"createdDate": "2019-05-19T00:00:00.000+0000"
},
{
"id": 4,
"name": "Haily Eilers",
"userName": "heilers3",
"profileImage": "https://source.unsplash.com/random/400x400",
"createdDate": "2019-06-28T00:00:00.000+0000"
},
{
"id": 5,
"name": "Oralie Polkinhorn",
"userName": "opolkinhorn4",
"profileImage": "https://source.unsplash.com/random/400x400",
"createdDate": "2019-06-04T00:00:00.000+0000"
}
]
}
I am keeping the URL private on here but it is returning JSON above. So far this is my code:
import UIKit
struct User: Codable {
let id: Int
let name: String
let userName: String
let profileImage: String
let createdDate: String
}
struct Users: Codable {
let users: String
}
let url = URL(string: "")!
URLSession.shared.dataTask(with: url) { data, _, _ in
if let data = data {
let users = try? JSONDecoder().decode([User].self, from: data)
print(users)
}
}.resume()
I need to be able to access the User properties but I think the nesting is making it difficult for me. Any help is awesome!! Thank you!!
First of all: Catch always the DecodingError and print it. It tells you exactly what's wrong.
The error occurs because you are ignoring the root object Users. Your code works if you decode(Users.self.
My suggestions:
Decode createdDate as Date adding a appropriate date decoding strategy.
Decode profileImage as URL (for free).
Handle all errors.
struct Root : Decodable { // `Users` and `User` is too confusing
let users: [User]
}
struct User : Decodable {
let id: Int
let name: String
let userName: String
let profileImage: URL
let createdDate: Date
}
URLSession.shared.dataTask(with: url) { data, _, error in
if let error = error { print(error); return }
do {
let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let result = try decoder.decode(Root.self, from: data!)
for user in result.users {
print(user.userName, user.id, user.createdDate)
}
} catch {
print(error)
}
}.resume()
The root of the json is a dictionary not an array you can write a root class but it will be useless , so you need
URLSession.shared.dataTask(with: url) { data, _, _ in
do {
if let data = data {
let res = try JSONSerialization.jsonObject(with: data) as! [String:Any]
let usersData = try JSONSerialization.data(withJSONObject: res["users"])
let users = try JSONDecoder().decode([User].self, from: usersData)
print(users)
}
}
catch {
print(error)
}
}.resume()
Your Users struct (I renamed it to UsersResponse) should contain a users property of type [User]. Then you can do the following:
struct UsersResponse: Codable {
let users: [User]
}
URLSession.shared.dataTask(with: url) { data, _, _ in
guard let data = data else { return }
if let users = try? JSONDecoder().decode(Users.self, from: data).users {
users.forEach { user in
print("A user called \(user.name) with an id of \(user.id).")
}
}
}.resume()
Please try the below code. This is working for me.
Model Class:
struct UsersResponse: Codable {
let users: [User]
}
struct User: Codable {
let id: Int
let name: String
let userName: String
let profileImage: String?
let createdDate: String
}
Network class:
public enum EndPoints: String {
case prod = "ProdURL"
case test = "testURL"
}
public enum Result<T> {
case success(T)
case failure(Error)
}
final public class Networking: NSObject {
// MARK: - Private functions
private static func getData(url: URL,
completion: #escaping (Data?, URLResponse?, Error?) -> ()) {
URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
}
/// fetchUsersResponse function will fetch the User Response and returns
/// Result<UsersResponse> as completion handler
public static func fetchUsersResponse(shouldFail: Bool = false, completion: #escaping (Result<UsersResponse>) -> Void) {
var urlString: String?
if shouldFail {
urlString = EndPoints.test.rawValue
} else {
urlString = EndPoints.prod.rawValue
}
guard let mainUrlString = urlString, let url = URL(string: mainUrlString) else { return }
Networking.getData(url: url) { (data, response, error) in
if let error = error {
completion(.failure(error))
return
}
guard let data = data, error == nil else { return }
do {
let decoder = JSONDecoder()
//decoder.dateDecodingStrategy = .millisecondsSince1970
decoder.dateDecodingStrategy = .formatted(setDateFormat())
let json = try decoder.decode(UsersResponse.self, from: data)
completion(.success(json))
} catch let error {
completion(.failure(error))
}
}
}
func setDateFormat() -> DateFormatter {
let dateFormat = DateFormatter()
dateFormat.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
return dateFormat
}