3

I'm pretty new to developing apps and working with Xcode and Swift and trying to get a handle on how to decode JSON responses from an API. I'm also trying to follow MVVM practices.

I'm calling to an API that returns data structured like this:

"location": {
    "latitude": -27.4748,         
    "longitude": 153.017     },     
"date": "2020-12-21",     
"current_time": "21:55:42.198",     
"sunrise": "04:49",     
"sunset": "18:42",
"...": "..."
} 

My understanding is I need a struct to decode this information into. This is what my struct looks like (it may be incorrect):

struct Example: Decodable {
let location: Coordinates
let date: String
let current_time: String
let sunset: String
let sunset: String
let ... :String

struct Coordinates: Decodable{
    let latitude: Double
    let longitude: Double
    }
}

So I want to be able to call this api. I have the correct address to call it because the dashboard on the website I'm using is showing I've hit it. Could I get some guidance on how to call it and decode the response? Currently I'm doing something like this:

if let url = URL(string: "this is the api web address"){
    URLSession.shared.dataTask(with: url){ (with: data, options [] ) as?                 
[String:String]{
        print("(\json)" + "for debugging purposes")
}}
else{
    print("error")
}
.resume()

Thanks for any and all help. Again, my goal is to call this function, hit the API, decode the JSON response and store it into a variable I can use in my views elsewhere. Thanks!

  • You should be able to find many similar questions here on stackoverflow and online articles and tutorials that explains how to do this – Joakim Danielson May 21 '21 at 20:04
  • what is this `URLSession.shared.dataTask(with: url){ (with: data, options [] ) as? [String:String]{` ? Are you sure it does compile? kkk looks like you are mixing JSONSerialization with Codable and URLSession – Leo Dabus May 21 '21 at 20:04
  • Does this answer your question? [URLSession.shared.dataTask correct way to receive data](https://stackoverflow.com/questions/54959996/urlsession-shared-datatask-correct-way-to-receive-data) – Joakim Danielson May 21 '21 at 20:06
  • @JoakimDanielson OP is using SwiftUI I think besides the decoding part he is probably having a hard time with the MVVM – Leo Dabus May 21 '21 at 20:07
  • @LeoDabus I think including SwiftUI and MVVM here would make it to broad so one thing at a time and OP writes _"Could I get some guidance on how to call it and decode the response"_ right above the last code section so I think my duplicate link is appropriate – Joakim Danielson May 21 '21 at 20:09
  • @LeoDabus I think I may have that mixed up after all. I'm reading a book on SwiftUI and xcode development and the example I'm learning from seems to be for JSONSerialization – Dakota Long May 21 '21 at 20:19
  • @DakotaLong I could tell. Forget about JSONSerialization. I will post some Codable examples. – Leo Dabus May 21 '21 at 20:21

1 Answers1

3

You are mixing JSONSerialization with Codable and URLSession. Forget about JSONSerialization. Try to focus on Codable protocol.

struct Example: Decodable {
    let location: Coordinates
    let date: String
    let currentTime: String
    let sunrise: String
    let sunset: String
}

struct Coordinates: Decodable {
    let latitude: Double
    let longitude: Double
}

This is how you can decode your json data synchronously

extension JSONDecoder {
    static let shared: JSONDecoder = {
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        decoder.dateDecodingStrategy = .iso8601
        return decoder
    }()
}

extension Data {
    func decodedObject<T: Decodable>() throws -> T {
        try JSONDecoder.shared.decode(T.self, from: self)
    }
}

Playground testing

let json = """
{
    "location": {
        "latitude": -27.4748,
        "longitude": 153.017     },
    "date": "2020-12-21",
    "current_time": "21:55:42.198",
    "sunrise": "04:49",
    "sunset": "18:42",
}
"""

let data = Data(json.utf8)

do {
    let example: Example = try data.decodedObject()
    print(example)
} catch {
    print(error)
}

And fetching your data asynchronously


extension URL {
    func getResult<T: Decodable>(completion: @escaping (Result<T, Error>) -> Void) {
        URLSession.shared.dataTask(with: self) { data, response, error in
            guard let data = data, error == nil else {
                completion(.failure(error!))
                return
            }
            do {
                completion(.success(try data.decodedObject()))
            } catch {
                completion(.failure(error))
            }
        }.resume()
    }
}

let url = URL(string: "https://www.example.com/whatever")!

url.getResult { (result: Result<Example, Error>) in
    switch result {
    case let .success(example):
        print(example)
    case let .failure(error):
        print(error)

    }
}

If you need help with your SwiftUI project implementing MVVM feel free to open a new question.

Leo Dabus
  • 216,610
  • 56
  • 458
  • 536
  • 1
    This is excellent help, thank you!! I read up on some more examples and worked with your examples to get things working. – Dakota Long May 22 '21 at 01:32
  • I do have a question regarding the Data extension. The Data extension uses a "data" variable in the JSONDecoder try area. Where would it get that variable from since it's not in its scope? – Dakota Long May 22 '21 at 23:51
  • @DakotaLong sorry my bad should be `self` – Leo Dabus May 23 '21 at 00:20
  • I posted a second question on the best practices way of storing the data into a struct. If you have time to look at it I'd greatly appreciate it :) – Dakota Long May 25 '21 at 00:27