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!

Dakota Long
  • 45
  • 1
  • 7
  • 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

2 Answers2

4

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
  • 229,809
  • 59
  • 489
  • 571
  • 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
-1

Correct Method to decode a local JSON file in SwiftUI

do {
        let path = Bundle.main.path(forResource: "filename", ofType: "json")
        let url = URL(fileURLWithPath: path!)
        let data = try Data(contentsOf: url)
        let decodedresults = try JSONDecoder().decode(struct_name.self, from: data)
        print(decodedresults)

    } catch {
        print(error)
    }
  • This is just Swift, it does not involve SwiftUI, and doesn't address OP's issue. – Eric Aya Jun 21 '22 at 17:07
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/32072953) – dsillman2000 Jun 25 '22 at 19:12