#arrays #swift #class #key-value
Вопрос:
Я написал некоторый код, который успешно считывает файл JSON в то, что я считаю массивом пар значений ключей с 5 значениями, связанными с каждым ключом. Файл JSON считывается во время создания класса и, похоже, работает. В contentView я хотел бы иметь возможность просматривать ключи (которые являются датами) и значения, возможно, с помощью цикла ForEach. К сожалению, я не могу получить доступ к массиву пар ключ-значение из представления содержимого. Инструкция print в классе выводит то, что я ожидал бы. В представлении содержимого arrayname.count возвращает 0. Я бы предположил, что упускаю из виду какую-то простую вещь, но иногда, когда вы смотрите на что-то слишком долго, вы упускаете очевидное. Если бы кто-нибудь мог указать мне правильное направление, я был бы признателен.
ForEach был моим способом попытаться определить, как получить доступ к отдельным элементам vooData, таким как дата, которая является ключом, или определенное значение, такое как закрытие.
Ниже приведен мой тестовый код. С уважением, Крис
Ниже приведены примеры моего файла JSON и результата инструкции print в классе ReadData.
{
"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)
})
}
}
}
Комментарии:
1. Есть несколько вещей, которые я могу здесь обсудить, но я обеспокоен тем, что это
JSONDecoder().decode(Stock.self, from: data!)
действительно работает. Похоже, на самом деле ты хочешь именно[Stock]
этого . Можете ли вы включить образец фактических данных JSON? И, может быть, опишите, что вы пытаетесь увидеть в своемForEach
?2. То, что вы опубликовали, не является допустимым JSON. Не могли бы вы, пожалуйста, опубликовать фактический файл JSON, который вы пытаетесь декодировать?
Ответ №1:
JSON, который вы включили, на самом деле недействителен. Я делаю предположение, что на самом деле это выглядит примерно так:
{
"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"
}
}
}
Если это так, то вы можете внести некоторые небольшие изменения в свой код и заставить его работать.
- Похоже, что вы изначально пытались использовать
ObservableObject
-это то, что вы должны использовать.
- В исходном коде вы использовали
let tmpData =
, но никогда не устанавливалиself.tmpData
- Использование
do/try/catch
вместоtry?
предпочтительно, потому что в случае возникновения ошибки вы действительно можете распечатать ее. - Неясно, что именно вы пытаетесь отобразить, но я включил пример a
ForEach
, который преобразует словарь в пары ключ/значение, а затем отображает цену открытия.
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)")
}
}
}
}
}
Комментарии:
1. Спасибо. Можете ли вы объяснить комментарий о том, что вы никогда не устанавливаете self.tmpData. Я предполагаю, что это может быть частью причины, по которой contentView не получил данные. Кроме того, являются ли данные, содержащиеся в tmpData, массивом пар ключевых значений? Если да, то ваше преобразование берет его из массива пар ключ-значение и преобразует в словарь, где есть только ключи и значения?
2. «Можете ли вы объяснить комментарий о том , чтобы никогда не устанавливать self.tmpData» Конечно, если вы не установите
self.tmpData
, он никогда не получит значения.let tmpData
это не то же самое, чтоself.tmpData
… область действияlet tmpData
предназначена только для этой функции.self.tmpData
задает свойство для класса.3. «являются ли данные, содержащиеся в tmpData, массивом пар ключевых значений» — в основном. Это
Dictionary
. Но словарь-это не просто «массив пар ключ-значение» — это то, что яmap
делал раньше.4. Если это ответ на ваш вопрос, пожалуйста, используйте зеленую галочку, чтобы принять его, а также подумайте о том, чтобы проголосовать с помощью стрелки, если вы нашли ответ полезным.
Ответ №2:
Я думаю, что у вас есть небольшая ошибка в вашем init
У тебя есть
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!)
}
}
но я не думаю, что вам нужны let
данные tmpData внутри loadData
. Я думаю, что вы намерены назначить это переменной экземпляра, а не локальной переменной.
Попробуй:
//
// 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")
}
}
Комментарии:
1. Вы совершенно правы в отношении параметра
let tmpData
vs, устанавливающего свойство класса, о котором я также говорил в своем ответе. Однако в вашем ответе не учитывается тот факт, что операция использовала асинхронный URL-запрос для получения данных JSON, и заменяет его несинхронным вызовом. Без аннотированияReadData
сObservableObject
,ContentView
он не будет знать, чтоtmpData
было установлено, и не будет корректно отображать данные.2. Спасибо всем вам. Я многому научился, и теперь у меня есть функционирующий код.