JohnSF
JohnSF

Reputation: 4300

SwiftUI JSON Decode with enum Element

I am struggling with the decode of JSON data that has an array of dictionaries as a subunit. With the help of online parsers, I have established the structure and written the format as follows. I do get the data, but only wrapped in my enum design. I have not been able to get the actual values. Seems like I am missing something simple.

This is the structure that the data presents:

{
  "series": [
    {
      "series_id": "EBA.US48-ALL.D.H",
      "name": "Demand for United States Lower 48 (region), hourly - UTC time",
      "data": [
        [
          "20210518T20Z",
          474131
        ],
        [
          "20210518T19Z",
          468466
        ],
        [
          "20210518T18Z",
          462003
        ]
      ]
    }
  ]
}

I created the structure as this:

    // MARK: - TopLevel
    struct TopLevel: Codable {
        let series: [Series]
    }//top level

    // MARK: - Series
    struct Series: Codable {
        let seriesID, name: String
        let data: [[Datum]]

        enum CodingKeys: String, CodingKey {
            case seriesID = "series_id"
            case name, data
        }
    }// series

    enum Datum: Codable {
        case dInteger(Int)
        case dString(String)

        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            if let x = try? container.decode(Int.self) {
                self = .dInteger(x)
                return
            }
            if let x = try? container.decode(String.self) {
                self = .dString(x)
                return
            }
            throw DecodingError.typeMismatch(Datum.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Datum"))
        }

        func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            switch self {
            case .dInteger(let x):
                try container.encode(x)
            case .dString(let x):
                try container.encode(x)
            }
        }
    }// enum datum
    
And my DataStore:

    class DataStore: ObservableObject {
    
        static let shared = DataStore()
    
        private var topLevelPublisher: AnyPublisher<TopLevel, Error>?
        var topLevelCancellable: AnyCancellable?
    
        @Published var netGenYears: [NetGenYear] = []
        @Published var topLevels: [TopLevel] = []
        //@Published var instantaneousDemands: [InstantaneousDemand] = []

        init() {
            getTopLevel()
        
        }//init

        func getTopLevel() {
            self.topLevelPublisher = Webservice().fetchHourDemand()
            guard let pub = self.topLevelPublisher else { return }
        
            topLevelCancellable = pub
                .sink(receiveCompletion: {completion in
                
                    switch completion {
                    case .finished:
                        print("no .sink error")
                    case .failure(let anError):
                        print("received an error: , ", anError)
                    }
                  
                }, receiveValue: { (tl) in
                
                    self.topLevels.append( tl )
                    print("topLevels is \(self.topLevels)")
                
                })
        }//get top level
    }//class
    
Then I want to make a graph and put the values into the bars instead of these constants:

    struct InstantaneousDemandView: View {
    
        @ObservedObject var dataStore = DataStore.shared
    
        var body: some View {
            VStack {
            
                Text("US Most Recent Hour Demand")          
            
                HStack(alignment: .bottom) {
                MyBarRectangle(w: 400, count: 4, maxValue: 600000, total: 550000, color: Color.blue)
                MyBarRectangle(w: 400, count: 4, maxValue: 600000, total: 500000, color: Color.yellow)
                MyBarRectangle(w: 400, count: 4, maxValue: 600000, total: 450000, color: Color.green)
                MyBarRectangle(w: 400, count: 4, maxValue: 600000, total: 350000, color: Color.red)
                }
                Text("Thousand Megawatt Hours")
 
            }
            .onAppear {
                print(dataStore.topLevels.count)
                print(dataStore.topLevels[0].series.count)
                print(dataStore.topLevels[0].series[0].data[0])
                print(dataStore.topLevels[0].series[0].data.count)
            
                print("and one data point")
                print(dataStore.topLevels[0].series[0].data[0].first ?? "none")
                print(dataStore.topLevels[0].series[0].data[0])
            
            }
        }
    }

And this is in the console from the print statements:

topLevels is [Juice4U.TopLevel(series: [Juice4U.Series( seriesID: "EBA.US48-ALL.D.H", name: "Demand for United States Lower 48 (region), hourly - UTC time", data: [ [Juice4U.Datum.dString("20210522T04Z"), Juice4U.Datum.dInteger(438877)], [Juice4U.Datum.dString("20210522T03Z"), Juice4U.Datum.dInteger(462966)], [Juice4U.Datum.dString("20210522T02Z"), Juice4U.Datum.dInteger(481277)], [Juice4U.Datum.dString("20210522T01Z"), Juice4U.Datum.dInteger(492144)] ])])] no .sink error 1 1 [Juice4U.Datum.dString("20210522T04Z"), Juice4U.Datum.dInteger(438877)] 4 and one data point dString("20210522T04Z") [Juice4U.Datum.dString("20210522T04Z"), Juice4U.Datum.dInteger(438877)]

I can't seem to extract the string - like 20210522T04Z and the integer 438877.

Any guidance would be appreciated.

Upvotes: 0

Views: 138

Answers (1)

vadian
vadian

Reputation: 285082

…that has an array of dictionaries as a subunit

No, the type of data is an array of heterogenous arrays, so it's better to decode Datum as struct with an unkeyedContainer rather than an enum

struct Datum: Codable {
    let stringValue : String
    let intValue : Int
    
    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        stringValue = try container.decode(String.self)
        intValue = try container.decode(Int.self)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.unkeyedContainer()
        try container.encode(stringValue)
        try container.encode(intValue)
    }
}// struct datum

and declare data in Series

let data: [Datum]

Upvotes: 1

Related Questions