当每个键有 5 个值时,将字典值从字符串转换为双精度值

Convert dictionary values from string to double when there are 5 values per key

我正在尝试学习如何使用 Swift 中的词典。我的程序读取一个本地 JSON 文件,其中每个键包含五个值元素,并将其放入字典中。键是字符串格式“yyyy-MM-dd”的日期,值表示字符串格式的 409.34593 美元。我已经能够对字典进行排序和过滤了。

此外,使用 mapValues() 创建一个新字典,其中包含一个键和一个值,并将值转换为可选的双精度值。令我困惑的是如何将所有 5 个值元素转换为双精度,即具有与从 JSON 文件中读取的相同的字典,但所有 5 个值都是双精度或可选双精度。

下面是 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"
    },
    "2021-09-21": {
        "1. open": "402.6600",
        "2. high": "403.9000",
        "3. low": "399.4400",
        "4. close": "400.0400",
        "5. volume": "6418124"
    },

struct Stock: Codable {
    let timeSeriesDaily: [String : TimeSeriesDaily]  // creates a dictionary, key = string : TimeSeriesDaily = value
    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: ObservableObject  {
    @Published var tmpData = Stock(timeSeriesDaily : [:])  // initialize dictionary as empty when struct Stock is created
    // type info has to be made availabe upon creation, is done in the Stock struct
    init() {
        loadData()
    }
    func loadData() {
        guard let url = Bundle.main.url(forResource: "VOO", withExtension: "json")
        else {
            print("Json file not found")
            return
        }
        let decoder = JSONDecoder()
        do {
            let data = try Data(contentsOf: url)
            self.tmpData = try decoder.decode(Stock.self, from: data)
//            print(self.tmpData)
        } catch {
            print(error)
        }
    }
}
struct ContentView: View {
    @ObservedObject var vooData = ReadData()
    var body: some View {
        ScrollView  {
            VStack (alignment: .leading) {
                let lastYear = getOneYearAgo()
                let filteredDict = vooData.tmpData.timeSeriesDaily.filter { [=10=].key > lastYear } // Works
                let sortedFilteredDict = filteredDict.sorted { [=10=].key < .key }                 // Works
                let justCloseArray = sortedFilteredDict.map { ([=10=].key, [=10=].value.Close) }           // returns array [(String, String)]
                let justCloseDict = Dictionary(uniqueKeysWithValues: justCloseArray)                // returns dictionary with 1 key & 1 val
                let sortedCloseDict = justCloseDict.sorted { [=10=].key < .key }                      // works
                let newDict = filteredDict.mapValues { Double([=10=].Close) }
                let sortedNewDict = newDict.sorted { [=10=].key < .key }
                Spacer()
                //                ForEach ( sortedCloseDict.map { ([=10=].key, [=10=].value) }, id: \.0 ) { keyValuePair in
                ForEach ( sortedNewDict.map { ([=10=].key, [=10=].value) }, id: \.0 ) { keyValuePair in   // map converts dictionary to arrap
                    HStack {
                        Text (keyValuePair.0)
                        Text ("\(keyValuePair.1!)")
                    }
                }
                Spacer()
            } // end vstack
        } .frame(width: 600, height: 400, alignment: .center) // end scroll view
    }
}

这里的问题是你的美元值不是数字,它们是字符串。

把它放在 Playground 中:

import Foundation

let jsonData = """
{
  "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"
    },
    "2021-09-21": {
      "1. open": "402.6600",
      "2. high": "403.9000",
      "3. low": "399.4400",
      "4. close": "400.0400",
      "5. volume": "6418124"
    }
  }
}
"""
    .data(using: .utf8)!

struct ReadData {
    let timeSeriesDaily: [Date: TimeSeriesDaily]
}

struct TimeSeriesDaily {
    let open: Double
    let high: Double
    let low: Double
    let close: Double
    let volume: Double
}

extension ReadData: Decodable {
    enum CodingKeys: String, CodingKey {
        case timeSeriesDaily = "Time Series (Daily)"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        timeSeriesDaily = Dictionary(
            uniqueKeysWithValues: try container
                .decode([String: TimeSeriesDaily].self, forKey: .timeSeriesDaily)
                .map { (dateFormatter.date(from: [=10=])!, ) }
        )
    }
}
/** In the above, the container can decode [String: T] but it can't decode [Date: T], so we convert the Strings to Dates using a dateFormatter */

extension TimeSeriesDaily: Decodable {
    enum CodingKeys: String, CodingKey {
        case open = "1. open"
        case high = "2. high"
        case low = "3. low"
        case close = "4. close"
        case volume = "5. volume"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        open = Double(try container.decode(String.self, forKey: .open))!
        high = Double(try container.decode(String.self, forKey: .high))!
        low = Double(try container.decode(String.self, forKey: .low))!
        close = Double(try container.decode(String.self, forKey: .close))!
        volume = Double(try container.decode(String.self, forKey: .volume))!
    }
}
/** Since the values in the json are actually Strings, we decode them as such and then convert to Doubles. */
let dateFormatter: DateFormatter = {
    let result = DateFormatter()
    result.dateFormat = "yyyy-MM-dd"
    return result
}()

print(try JSONDecoder().decode(ReadData.self, from: jsonData))

如果你不喜欢所有的force casting,你可以引入一个castOrThrow类型的函数。