iosjsonswiftlistview

Swift JSON Nested out put for ListView


I am trying to display the contents of the result. The Data is returned as JSON Array. I created a view model "Stocks" and want to access the "results". Currently it compiles but the data does not show up.

Help would be highly appreciated.

import SwiftUI
struct Stocks: Hashable, Codable{
    var results: [Results]
    var status: String
    
    struct Results: Hashable, Codable{
        var ticker: String
        var name: String
        var market: String
        var locale: String
        var primary_exchange: String
        var type: String
        var active: Bool
        var currency_name: String
        var cik: String
        var composite_figi: String
        var share_class_figi: String
        var last_update_utc: String
    }
}


class ViewModel: ObservableObject{
    @Published var stocks: [Stocks] = []
    func fetch(){
        guard let url = URL(string: "https://api.polygon.io/v3/reference/tickers?market=stocks&active=true&apiKey=<apikey>") else{return}
        
        let task = URLSession.shared.dataTask(with: url) {[weak self]data, _, error in
            guard let data = data, error == nil else{
                return
            }
            // Convert JSON
            do{
                let stocks = try JSONDecoder().decode([Stocks].self, from: data)
                DispatchQueue.main.async{
                    self?.stocks = stocks
                }
               
            }
            catch{
                print(error)
            }
        }
        task.resume()
    }
}

struct ContentView: View {
    @StateObject var viewModel = ViewModel()

    
    var body: some View {
        NavigationView{
            List{
                ForEach(viewModel.stocks, id: \.self){resu in
                    ForEach(resu.results, id: \.self){st in
                        Text(st.currency_name)
                    }
                }
                
            }
        }.navigationTitle("Stocks")
        .onAppear{
            viewModel.fetch()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Here is the Response object:

{
    "results": [
        {
            "ticker": "A",
            "name": "Agilent Technologies Inc.",
            "market": "stocks",
            "locale": "us",
            "primary_exchange": "XNYS",
            "type": "CS",
            "active": true,
            "currency_name": "usd",
            "cik": "0001090872",
            "composite_figi": "BBG000C2V3D6",
            "share_class_figi": "BBG001SCTQY4",
            "last_updated_utc": "2022-12-20T00:00:00Z"
        },
        {
            "ticker": "AA",
            "name": "Alcoa Corporation",
            "market": "stocks",
            "locale": "us",
            "primary_exchange": "XNYS",
            "type": "CS",
            "active": true,
            "currency_name": "usd",
            "cik": "0001675149",
            "composite_figi": "BBG00B3T3HD3",
            "share_class_figi": "BBG00B3T3HF1",
            "last_updated_utc": "2022-12-20T00:00:00Z"
        },

I created a view model "Stocks" and want to access the "results". Currently it compiles but the data does not show up.


Solution

  • The naming of your structs is largely confusing.

    According to the JSON you are going to receive one root object containing an array of Stock (supposed to be named in singular form) objects for key results.

    And there is a struct member last_update_utc which does not match the key last_updated_utc.

    Name your structs this way, I renamed the struct members as camelCase and as constants (let) and you can decode lastUpdatedUtc as Date with the .iso8601 strategy

    struct Response: Hashable, Decodable {
        let results: [Stock]
        let status: String
        
        struct Stock: Hashable, Decodable {
            let ticker: String
            let name: String
            let market: String
            let locale: String
            let primaryExchange: String
            let type: String
            let active: Bool
            let currencyName: String
            let cik: String
            let compositeFigi: String
            let shareClassFigi: String
            let lastUpdatedUtc: Date
        }
    }
    

    and decode the JSON

    class ViewModel: ObservableObject{
        @Published var stocks: [Response.Stock] = []
        func fetch() {
            guard let url = URL(string: "https://api.polygon.io/v3/reference/tickers?market=stocks&active=true&apiKey=<apikey>") else{return}
            
            let task = URLSession.shared.dataTask(with: url) {[weak self] data, _, error in
                if let error { print(error); return }
                // Convert JSON
                do{
                    let decoder = JSONDecoder()
                    decoder.keyDecodingStrategy = .convertFromSnakeCase
                    decoder.dateDecodingStrategy = .iso8601
                    let response = try decoder.decode(Response.self, from: data!)
                    DispatchQueue.main.async {
                        self?.stocks = response.results
                    }
                   
                }
                catch{
                    print(error)
                }
            }
            task.resume()
        }
    }
    

    I even recommend to use async/await

    @MainActor
    class ViewModel: ObservableObject{
        @Published var stocks: [Response.Stock] = []
        
        func fetch() {
            guard let url = URL(string: "https://api.polygon.io/v3/reference/tickers?market=stocks&active=true&apiKey=<apikey>") else{return}
            Task {
                do {
                    let (data, _) = try await URLSession.shared.data(from: url)
                    let decoder = JSONDecoder()
                    decoder.keyDecodingStrategy = .convertFromSnakeCase
                    decoder.dateDecodingStrategy = .iso8601
                    let response = try decoder.decode(Response.self, from: data)
                    self.stocks = response.results
                } catch {
                    print(error)
                }
            }
        }
    }