【问题标题】:How to parse JSON from Yahoo Finance in Swift for MacOS如何在 Swift for MacOS 中解析来自 Yahoo Finance 的 JSON
【发布时间】:2020-11-14 14:30:33
【问题描述】:

我一直在努力解决这个问题!我有 Alamofire 和 SwiftyJSON。我使用 Alamofire 从 Yahoo Finance 获取 JSON 结果,如下所示:

public func getYahooQuote(symbol: String) {
        let stockURL = "https://query1.finance.yahoo.com/v7/finance/quote?symbols=" + symbol
        let request = AF.request(stockURL, parameters: ["quoteResponse": "result"])
        request.responseData { (response) in
            guard let data = response.value else {return}
            do {
                let json = try JSON(data: data)
                
                print(json)
                let decoder = JSONDecoder()
                let stock = try decoder.decode(QuoteParent.self, from: data)
                print(stock)
            } catch {
                print(error)
            }
        }
    }

因此,该请求需要一个字符串变量符号,该符号将传递给函数。我得到的结果是一个打印这个的 JSON 对象: '

{
  "quoteResponse" : {
    "result" : [
      {
        "fiftyTwoWeekLow" : 164.93000000000001,
        "regularMarketVolume" : 33445281,
        "messageBoardId" : "finmb_8108558",
        "symbol" : "QQQ",
        "currency" : "USD",
        "regularMarketPreviousClose" : 258.00999999999999,
        "fiftyDayAverage" : 250.32285999999999,
        "exchange" : "NMS",
        "quoteType" : "ETF",
        "regularMarketDayLow" : 251.31999999999999,
        "averageDailyVolume10Day" : 46768962,
        "fiftyTwoWeekHighChange" : -15.310013,
        "priceHint" : 2,
        "twoHundredDayAverageChange" : 31.669998,
        "exchangeTimezoneName" : "America\/New_York",
        "bookValue" : 188.77500000000001,
        "firstTradeDateMilliseconds" : 921076200000,
        "averageDailyVolume3Month" : 42292663,
        "tradeable" : false,
        "bidSize" : 8,
        "sourceInterval" : 15,
        "regularMarketChange" : -3.530014,
        "triggerable" : true,
        "longName" : "Invesco QQQ Trust",
        "market" : "us_market",
        "exchangeTimezoneShortName" : "EDT",
        "regularMarketDayHigh" : 256.93000000000001,
        "marketCap" : 100036083712,
        "gmtOffSetMilliseconds" : -14400000,
        "fiftyTwoWeekHighChangePercent" : -0.056747886999999997,
        "askSize" : 10,
        "language" : "en-US",
        "marketState" : "REGULAR",
        "fiftyTwoWeekRange" : "164.93 - 269.79",
        "twoHundredDayAverage" : 222.81,
        "trailingAnnualDividendRate" : 1.54,
        "quoteSourceName" : "Delayed Quote",
        "trailingThreeMonthReturns" : 30.27,
        "fiftyDayAverageChange" : 4.1571350000000002,
        "shortName" : "Invesco QQQ Trust, Series 1",
        "fiftyDayAverageChangePercent" : 0.016607093,
        "region" : "US",
        "regularMarketTime" : 1595609084,
        "priceToBook" : 1.3480599,
        "regularMarketOpen" : 254.12,
        "fiftyTwoWeekLowChange" : 89.549999999999997,
        "regularMarketDayRange" : "251.32 - 256.93",
        "trailingAnnualDividendYield" : 0.0059687606999999998,
        "fullExchangeName" : "NasdaqGS",
        "regularMarketChangePercent" : -1.3681694,
        "trailingPE" : 65.335044999999994,
        "fiftyTwoWeekHigh" : 269.79000000000002,
        "bid" : 254.56,
        "epsTrailingTwelveMonths" : 3.895,
        "trailingThreeMonthNavReturns" : 30.210000000000001,
        "fiftyTwoWeekLowChangePercent" : 0.54295766000000001,
        "twoHundredDayAverageChangePercent" : 0.14213903,
        "ask" : 254.61000000000001,
        "esgPopulated" : false,
        "regularMarketPrice" : 254.47999999999999,
        "sharesOutstanding" : 393100000,
        "financialCurrency" : "USD",
        "exchangeDataDelayedBy" : 0,
        "ytdReturn" : 16.809999999999999
      }
    ],
    "error" : null
  }
}

我有这样的 Codable 结构:

struct QuoteParent: Codable {
    var quoteResponse: QuoteResponse
}

struct QuoteResponse: Codable {
    var error: QuoteError?
    var result: Stock?
}

struct QuoteError: Codable {
    var lang: String?
    var description: String?
    var message: String?
    var code: Int
}
        
struct Stock: Codable {
        var ask : Decimal
        var askSize : Int
        var averageDailyVolume10Day : Int
        var averageDailyVolume3Month : Int
        var bid : Double
        var bidSize : Int
        var bookValue : Decimal
        var currency : String
        var epsTrailingTwelveMonths : Decimal
        var esgPopulated : Bool
        var exchange : String
        var exchangeDataDelayedBy : Int
        var exchangeTimezoneName : String
        var exchangeTimezoneShortName : String
        var fiftyDayAverage : Decimal
        var fiftyDayAverageChange : Decimal
        var fiftyDayAverageChangePercent : Decimal
        var fiftyTwoWeekHigh : Decimal
        var fiftyTwoWeekHighChange : Decimal
        var fiftyTwoWeekHighChangePercent : Decimal
        var fiftyTwoWeekLow : Decimal
        var fiftyTwoWeekLowChange : Decimal
        var fiftyTwoWeekLowChangePercent : Decimal
        var fiftyTwoWeekRange : String?
        var financialCurrency : String
        var firstTradeDateMilliseconds : Int
        var fullExchangeName : String
        var gmtOffSetMilliseconds : Int
        var language : String
        var longName : String
        var market : String
        var marketCap : Int
        var marketState : String
        var messageBoardId : String
        var priceHint : Int
        var priceToBook : Decimal
        var quoteSourceName : String
        var quoteType : String
        var region : String
        var regularMarketChange : Int
        var regularMarketChangePercent : Decimal
        var regularMarketDayHigh : Decimal
        var regularMarketDayLow : Decimal
        var regularMarketDayRange : String
        var regularMarketOpen : Double
        var regularMarketPreviousClose : Decimal
        var regularMarketPrice : Decimal
        var regularMarketTime : Int
        var regularMarketVolume : Int
        var sharesOutstanding : Int
        var shortName : String
        var sourceInterval : Int
        var symbol : String
        var tradeable : Bool
        var trailingAnnualDividendRate : Double
        var trailingAnnualDividendYield : Decimal
        var trailingPE : Decimal
        var trailingThreeMonthNavReturns : Decimal
        var trailingThreeMonthReturns : Decimal
        var triggerable : Bool
        var twoHundredDayAverage : Double
        var twoHundredDayAverageChange : Decimal
        var twoHundredDayAverageChangePercent : Decimal
        var ytdReturn : Decimal
    }

我尝试使用 JSONDecoder 对其进行解码,但这似乎需要一个 Data 对象,而我得到的对象是 JSON。

我使用这一行将 JSON 对象缩小为 result 的值,如下所示:

let json2 = json["quoteResponse"]["result"]

现在这仍然只是一个 JSON 对象,它确实包含我想要的所有数据,但我无法弄清楚如何将该 JSON 对象解析为我拥有的 Struct 类。这里的任何智慧都将不胜感激!

我确实尝试过这个来获取 JSON:

request.responseData { (response) in

而不是

request.responseJSON { (response) in

并尝试使用以下代码对其进行解码:

let decoder = JSONDecoder()
let stock = try decoder.decode(Stock.self, from: data)

但现在我得到这样的错误打印:

typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "quoteResponse", intValue: nil), CodingKeys(stringValue: "result", intValue: nil) ], debugDescription: "预期解码 Dictionary 但找到了一个数组。",底层错误: nil))

【问题讨论】:

  • 请注意所有数字类型都是错误的。只有双引号中的数字是String,浮点值是Double,其他是Inttruefalse(不是双引号)是Bool。而且CodableSwiftyJSON 要好得多。声明所有非可选内容,阅读您遇到的全面错误并解决问题。
  • 谢谢!很好地抓住了 stuct 属性,我会改变那些。完成后,如何解码 JSON 对象?我尝试过使用 JSONDecoder(),但这似乎需要一个 Data 对象,而不是 JSON 对象。
  • .responseData替换.responseJSON
  • 谢谢,我这样做了,我得到的错误是:数据无法读取,因为它丢失了。
  • 好吧,print(error)catch 块中,而不是其他任何东西。它告诉你出了什么问题。提示:缺少根对象(带有quoteResponse 键)。

标签: json swift alamofire swifty-json


【解决方案1】:

错误描述性很强:对象中键result的值对于键quoteResponse

[CodingKeys(stringValue: "quoteResponse", intValue: nil), CodingKeys(stringValue: "result", intValue: nil)]

不是字典,是数组

应解码 Dictionary 但找到了一个数组

所以改变

let result: [Stock]

您也可以将所有其他属性声明为常量 (let)。

【讨论】:

    【解决方案2】:

    您可以使用 quicktype.io 之类的工具从 JSON 生成 Codable 类型,所以我建议您使用它开始并从那里开始。

    我还建议您使用 Alamofire 的 responseDecodable 在您拥有 Decodable 类型后解析您的回复。

    AF.request(...).responseDecodable(of: YourType.self) { response in
        // Handle response.
    }
    

    【讨论】:

      【解决方案3】:

      真的要感谢大家的帮助,尤其是 vadian。这是基于 vadian 建议的最终工作代码。

      首先,这是新的获取雅虎财经报价功能:

      func getYahooQuote(symbol: String, completion: @escaping (QuoteParent) -> Void) {
          var quoteParent = QuoteParent()
          let stockURL = "https://query1.finance.yahoo.com/v7/finance/quote?symbols=" + symbol
          let request = AF.request(stockURL, parameters: ["quoteResponse": "result"])
          request.responseData { (response) in
              guard let data = response.value else {return}
              do {
                  let json = try JSON(data)
                  print(json)
                  let decoder = JSONDecoder()
                  quoteParent = try decoder.decode(QuoteParent.self, from: data)
                  completion(quoteParent)
              } catch {
                  print(error)
              }
          }
      }
      

      这是保存数据的更新结构:

      struct QuoteParent: Codable {
          var quoteResponse: QuoteResponse
          init() {
              quoteResponse = QuoteResponse()
          }
      }
      
      struct QuoteResponse: Codable {
          var error: QuoteError?
          var result: [Stock]?
          init() {
              error = nil
              result = []
          }
      }
      
      struct QuoteError: Codable {
          var lang: String?
          var description: String?
          var message: String?
          var code: Int?
          init() {
              lang = ""
              description = ""
              message = ""
              code = 0
          }
      }
      
      struct Stock: Codable {
          var ask : Decimal?
          var askSize : Int?
          var averageDailyVolume10Day : Int?
          var averageDailyVolume3Month : Int?
          var bid : Double?
          var bidSize : Int?
          var bookValue : Decimal?
          var currency : String?
          var epsTrailingTwelveMonths : Decimal?
          var esgPopulated : Bool?
          var exchange : String?
          var exchangeDataDelayedBy : Int?
          var exchangeTimezoneName : String?
          var exchangeTimezoneShortName : String?
          var fiftyDayAverage : Decimal
          var fiftyDayAverageChange : Decimal?
          var fiftyDayAverageChangePercent : Decimal?
          var fiftyTwoWeekHigh : Decimal?
          var fiftyTwoWeekHighChange : Decimal?
          var fiftyTwoWeekHighChangePercent : Decimal?
          var fiftyTwoWeekLow : Decimal?
          var fiftyTwoWeekLowChange : Decimal?
          var fiftyTwoWeekLowChangePercent : Decimal?
          var fiftyTwoWeekRange : String?
          var financialCurrency : String?
          var firstTradeDateMilliseconds : Int?
          var fullExchangeName : String?
          var gmtOffSetMilliseconds : Int?
          var language : String?
          var longName : String?
          var market : String?
          var marketCap : Int?
          var marketState : String?
          var messageBoardId : String?
          var priceHint : Int?
          var priceToBook : Decimal?
          var quoteSourceName : String?
          var quoteType : String?
          var region : String?
          var regularMarketChange : Decimal?
          var regularMarketChangePercent : Decimal?
          var regularMarketDayHigh : Decimal?
          var regularMarketDayLow : Decimal?
          var regularMarketDayRange : String?
          var regularMarketOpen : Double?
          var regularMarketPreviousClose : Decimal?
          var regularMarketPrice : Decimal?
          var regularMarketTime : Int?
          var regularMarketVolume : Int?
          var sharesOutstanding : Int?
          var shortName : String?
          var sourceInterval : Int?
          var symbol : String?
          var tradeable : Bool?
          var trailingAnnualDividendRate : Double?
          var trailingAnnualDividendYield : Decimal?
          var trailingPE : Decimal?
          var trailingThreeMonthNavReturns : Decimal?
          var trailingThreeMonthReturns : Decimal?
          var triggerable : Bool?
          var twoHundredDayAverage : Double?
          var twoHundredDayAverageChange : Decimal?
          var twoHundredDayAverageChangePercent : Decimal?
          var ytdReturn : Decimal?
      }
      

      我决定将属性设为可选,因为我发现 JSON 结果并不总是具有相同的字段,例如 ETF 与共同基金。

      这是我从其他视图控制器实现该功能的方式...

      @IBAction func symbolAction(_ sender: NSTextField) {
          let investment = investmentsArrayController.selectedObjects[0] as! InvestmentMO
          if investment.symbol?.count == 5 && investment.symbol?.suffix(2) == "XX" {
              investment.investmentType = TypeOfInvestment.CASH
              investment.investmentTypeString = investment.investmentType.displayName
          } else if investment.symbol?.count == 5 && investment.symbol?.suffix(1) == "X" {
              investment.investmentTypeString = TypeOfInvestment.MF.displayName
          }
          app.myViewController.getYahooQuote(symbol: investment.symbol ?? "", completion: {(quoteParent) -> Void in
              let stock = quoteParent.quoteResponse.result?[0]
              investment.investmentName = stock?.longName?.uppercased() ?? ""
              investment.price = NSDecimalNumber(decimal: stock?.regularMarketPrice ?? stock?.ask ?? 0)
              investment.priceChange = NSDecimalNumber(decimal: stock?.regularMarketChange ?? 0)
              investment.priceChangePerc = NSDecimalNumber(decimal: stock?.regularMarketChangePercent ?? 0).dividing(by: 100)
              investment.prevPrice = NSDecimalNumber(decimal: (stock?.regularMarketPreviousClose ?? investment.price?.decimalValue) ?? 0)
          })
      }
      

      【讨论】:

      • 顺便说一句,上面的“投资”对象对应于我用来跟踪投资数据的核心数据模型。现在我可以从雅虎财经获得定价和其他详细信息。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-19
      • 1970-01-01
      • 2014-11-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多