Reputation: 4300
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
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