Home > Net >  Swift macOS Xcode : Access array of key value pairs created in a class from ContentView
Swift macOS Xcode : Access array of key value pairs created in a class from ContentView

Time:09-24

I have written some code that successfully reads a JSON file into what I believe is an array of key value pairs with 5 values associated with each key. The JSON file is read in during the creation of a class and appears to work. In ContentView I would like to be able to step through keys (which are dates) and values, probably with a ForEach loop. Unfortunately I am not able to access the array of key value pairs from the ContentView. The print statement in the Class prints out what I would expect. In the ContentView arrayname.count returns 0. I would guess I am overlooking some simple thing but sometimes when you look at something for too long you miss the obvious. If someone could point me in the right direction it would be appreciated.

The ForEach was my way of trying to determine how to access individual members of vooData such as the date, which is the key, or a specific value such as closing.

Below is my test code. Regards, Chris

Examples of my JSON file and the result of the print statement in the class ReadData are as follows.

{
    "Meta Data": {
        "1. Information": "Daily Prices (open, high, low, close) and Volumes",
        "2. Symbol": "VOO",
        "3. Last Refreshed": "2021-09-22",
        "4. Output Size": "Full size",
        "5. Time Zone": "US/Eastern"
    },
    "Time Series (Daily)": {
        "2021-09-22": {
            "1. open": "402.1700",
            "2. high": "405.8500",
            "3. low": "401.2600",
            "4. close": "403.9000",
            "5. volume": "5979811"
        },

Stock(timeSeriesDaily: [

"2011-07-22": ReadJSONData.TimeSeriesDaily
(Open: "61.5000", 
High: "61.6000", 
Low: "61.2000", 
Close: "61.5500", 
Volume: “180400"
), 

"2014-06-18": ReadJSONData.TimeSeriesDaily
(
Open: "178.5900", 
High: "179.9700", 
Low: "178.1700", 
Close: "179.8700", 
Volume: “501900"
), 

"2015-09-18": ReadJSONData.TimeSeriesDaily
(
Open: "180.4100", 
High: "182.0400", 
Low: "179.6900", 
Close: "180.1300", 
Volume: “3205186"
)
])



import SwiftUI
struct Stock: Codable {
    let timeSeriesDaily: [String: TimeSeriesDaily]
    enum CodingKeys: String, CodingKey {
        case timeSeriesDaily = "Time Series (Daily)"
    }
}
struct TimeSeriesDaily: Codable {
    let Open, High, Low, Close: String
    let Volume: String
    enum CodingKeys: String, CodingKey {
        case Open = "1. open"
        case High = "2. high"
        case Low = "3. low"
        case Close = "4. close"
        case Volume = "5. volume"
    }
}
class ReadData {
//class ReadData: ObservableObject  {
//    @Published var tmpData  = [Stock]()
    var tmpData : [Stock] = []
    init() {
        loadData()
    }
    func loadData() {
        guard let url = Bundle.main.url(forResource: "VOO", withExtension: "json")
        else {
            print("Json file not found")
            return
        }
        let data = try? Data(contentsOf: url)
        let tmpData = try? JSONDecoder().decode(Stock.self, from: data!)
        print(tmpData!)
    }
}
struct ContentView: View {
//    @ObservedObject var vooData = ReadData()
    let readClass = ReadData()
    @State var vooData : [Stock] = ReadData().tmpData
    var body: some View {
        VStack{
            Text("Hello, world!")
                .padding()
                .frame(width: 600, height: 400, alignment: .center)
                .onAppear(perform: {
                    print(vooData.count)
                })
        }
    }
}

CodePudding user response:

The JSON that you've included isn't really valid. I'm making an assumption that it actually looks something like this:

{
    "Time Series (Daily)": {
        "2021-09-22": {
            "1. open": "402.1700",
            "2. high": "405.8500",
            "3. low": "401.2600",
            "4. close": "403.9000",
            "5. volume": "5979811"
        }
    }
}

If that's the case, then you can make some slight alterations to your code and have it work.

  • It looks like you had initially tried to use ObservableObject -- that is what you should use.

  • In your original code, you used let tmpData = but you never set self.tmpData

  • Using do/try/catch instead of try? is preferable because you can actually print an error if one occurs.

  • It's unclear exactly what you're trying to display, but I included an example of a ForEach which converts the dictionary to key/value pairs and then displays the opening price.

struct TimeSeriesDaily: Codable {
    let Open, High, Low, Close: String
    let Volume: String
    enum CodingKeys: String, CodingKey {
        case Open = "1. open"
        case High = "2. high"
        case Low = "3. low"
        case Close = "4. close"
        case Volume = "5. volume"
    }
}
class ReadData: ObservableObject  {
    @Published var tmpData  = Stock(timeSeriesDaily: [:])
    
    init() {
        loadData()
    }
    
    func loadData() {
        guard let url = Bundle.main.url(forResource: "VOO", withExtension: "json")
        else {
            print("Json file not found")
            return
        }
        do {
            let data = try Data(contentsOf: url)
            self.tmpData = try JSONDecoder().decode(Stock.self, from: data)
            print(self.tmpData)
        } catch {
            print(error)
        }
    }
}
struct ContentView: View {
    @ObservedObject var vooData = ReadData()
    
    var body: some View {
        VStack{
            ForEach(vooData.tmpData.timeSeriesDaily.map { ($0.key,$0.value) }, id: \.0) { keyValuePair in
                VStack {
                    Text(keyValuePair.0)
                    Text("Open: \(keyValuePair.1.Open)")
                }
            }
        }
    }
}

CodePudding user response:

I think you have a slight error in your init

You have

class ReadData {
    var tmpData : [Stock] = []
    init() {
        loadData()
    }
    func loadData() {
        guard let url = Bundle.main.url(forResource: "VOO", withExtension: "json")
        else {
            print("Json file not found")
            return
        }
        let data = try? Data(contentsOf: url)
        let tmpData = try? JSONDecoder().decode(Stock.self, from: data!)

     // ^^^^^^^^^^^ - don't think this should be let

        print(tmpData!)
    }
}

but I don't think you want the let on the tmpData inside of loadData. I think you intend to assign this to the instance variable, not a local variable.

Try:

//
//  ContentView.swift
//  JSONSample
//
//  Created by Scott Thompson on 9/23/21.
//

import SwiftUI

let jsonData = """
{
    \"Meta Data\": {
        \"1. Information\": \"Daily Prices (open, high, low, close) and Volumes\",
        \"2. Symbol\": \"VOO\",
        \"3. Last Refreshe\": \"2021-09-22\",
        \"4. Output Size\": \"Full size\",
    \"5. Time Zone\": \"US/Eastern\"
    },
    \"Time Series (Daily)\": {
        \"2021-09-22\": {
            \"1. open\": \"402.1700\",
            \"2. high\": \"405.8500\",
            \"3. low\": \"401.2600\",
            \"4. close\": \"403.9000\",
            \"5. volume\": \"5979811\"
        },
    }
}
"""

struct Stock: Codable {
    let timeSeriesDaily: [String: TimeSeriesDaily]
    enum CodingKeys: String, CodingKey {
        case timeSeriesDaily = "Time Series (Daily)"
    }
}

struct TimeSeriesDaily: Codable {
    let Open, High, Low, Close: String
    let Volume: String
    enum CodingKeys: String, CodingKey {
        case Open = "1. open"
        case High = "2. high"
        case Low = "3. low"
        case Close = "4. close"
        case Volume = "5. volume"
    }
}

class ReadData {
    let tmpData : [Stock]
    init() {
        let data = jsonData.data(using: .utf8)

        if let parsedData = try? JSONDecoder().decode(Stock.self, from: data!) {
            tmpData = [parsedData]
        } else {
            tmpData = []
        }
    }
}

struct ContentView: View {
    @State var vooData : [Stock] = ReadData().tmpData
    var body: some View {
        VStack{
            Text("Hello, world!")
                .padding()
                .frame(width: 600, height: 400, alignment: .center)
                .onAppear(perform: {
                    debugPrint(vooData)
                    print(vooData.count)
                })
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .previewLayout(PreviewLayout.sizeThatFits)
            .padding()
            .previewDisplayName("Default preview")
    }
}
  • Related