Хотите просмотреть метаданные json

Я пытаюсь создать приложение, которое отображает курсы обмена валют, используя API Alpha Vantage для iOS. Я создал функции, но не могу понять, как получить доступ к точному значению json, которое равно «5. Обменный курс».

Вот некоторый код и данные json, которые помогут лучше объяснить:

Созданный URL:

func USDtoEUR(_ completionHandler: @escaping (_ success: Bool, _ quotes: [String:AnyObject]?, _ error: String?) -> Void) {

    let urlString = "https://www.alphavantage.co/query?function=CURRENCY_EXCHANGE_RATE&from_currency=USD&to_currency=EUR&apikey=NP3M8LL62YJDO0YX"
        let session = URLSession.shared
        let url = URL(string: urlString)!

        let request = URLRequest(url: url)
        let task = session.dataTask(with: request, completionHandler: { data, response, error in
            if error != nil {
                completionHandler(false, nil, error!.localizedDescription)
            }
            else {
                do {
                    let result = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments) as! NSDictionary
                    if let dictionary = result["Realtime Currency Exchange Rate"] as? [String:AnyObject]! {
                        completionHandler(true, dictionary, nil)
                    }
                    else {
                        completionHandler(false, nil, nil)
                    }
                } catch {
                    completionHandler(false, nil, "Unable to process retrieved data.")
                }
            }

        })
        task.resume()

    }

Котировки в контроллере представления

func usdQUotesRequest() {

  USDClient().USDtoEUR() { success, newQuote, error in

    if success {
        self.usdtoeurquote = newQuote
        DispatchQueue.main.async {
            self.stopActivityIndicator()
            self.Refresh.isEnabled = true
        }

    } else {
        DispatchQueue.main.async {
            self.displayAlert("Unable to Retrieve Latest Conversion Rates", message: "\(error!)")
            self.stopActivityIndicator()
            self.Refresh.isEnabled = true

        }
    }
}

// Котировки, отображаемые после нажатия на кнопку USD:

@IBAction func usdConversions(_ sender: Any) {

    self.displayAlert("Alert!", message: "USD Selected")

    let usdVal = (outputCurrency1.text! as NSString).floatValue

    let euroValue = usdVal * (usdtoeurquote["5. Exchange Rate"] as! Float)
    outputCurrency2.text = String(format: "%.2f", euroValue)

    let gbpVal = usdVal * (usdtogbpquote["5. Exchange Rate"] as! Float)
    outputCurrency3.text = String(format: "%.2f", gbpVal)

    let cnyVal = usdVal * (usdtocnyquote["5. Exchange Rate"] as! Float)
    outputCurrency2.text = String(format: "%.2f", cnyVal)

    let cadVal = usdVal * (usdtocadquote["5. Exchange Rate"] as! Float)
    outputCurrency2.text = String(format: "%.2f", cadVal)

    let inrVal = usdVal * (usdtoinrquote["5. Exchange Rate"] as! Float)
    outputCurrency2.text = String(format: "%.2f", inrVal)

    let sekVal = usdVal * (usdtosekquote["5. Exchange Rate"] as! Float)
    outputCurrency2.text = String(format: "%.2f", sekVal)

    let rubVal = usdVal * (usdtorubquote["5. Exchange Rate"] as! Float)
    outputCurrency2.text = String(format: "%.2f", rubVal)

    let nzdVal = usdVal * (usdtonzdquote["5. Exchange Rate"] as! Float)
    outputCurrency2.text = String(format: "%.2f", nzdVal)

}

Необработанные данные JSON: данные JSON на веб-странице


person lemonator21    schedule 22.07.2018    source источник
comment
Прежде всего, словарь JSON в Swift 3+ — это [String:Any]. Пожалуйста (научитесь) читать JSON. Это очень просто. Например, все в двойных кавычках String (без исключений). Таким образом, значение ключа 5. Exchange Rate равно String, а не Float. Далее я рекомендую использовать протокол Decodable для разбора JSON на структуры.   -  person vadian    schedule 22.07.2018
comment
Так что я не могу делать никаких расчетов со значением, которое я могу только загрузить?   -  person lemonator21    schedule 22.07.2018
comment
Можно, но нужно создать Double или Float из String (но не через NSStringfloatValue)   -  person vadian    schedule 22.07.2018
comment
Хорошо, спасибо, так почему каждый раз, когда я пытаюсь загрузить значения, он говорит, что не может найти необязательное значение? Правильно ли выполнена jsonserialization?   -  person lemonator21    schedule 22.07.2018
comment
Исключение возникает в usdConversions, потому что, как уже упоминалось, ставки являются строками, а не числами с плавающей запятой. Не используйте NSDictionary, приведите к [String:Any], а значение для ключа Realtime Currency Exchange Rate на самом деле будет более конкретным [String:String]. И опустите параметр options. .allowFragments в данном случае бессмысленно.   -  person vadian    schedule 22.07.2018
comment
Теперь я получаю ошибку другого типа: Не удалось преобразовать значение типа «__NSCFString» (0x10e857168) в «NSNumber. Не могли бы вы помочь с этим?   -  person lemonator21    schedule 22.07.2018
comment
Извините, что задаю так много вопросов, кстати, вы единственный, кто помогает, спасибо за это.   -  person lemonator21    schedule 22.07.2018
comment
Вы должны изменить много вещей. Я написал ответ с более удобным подходом.   -  person vadian    schedule 22.07.2018


Ответы (1)


Основная ошибка заключается в том, что значение для ключа 5. Exchange Rate равно String, а не Float, и код надежно дает сбой при принудительном развертывании.

В Swift 4 протокол Decodable гораздо удобнее, чем словарь.

Создайте две структуры. Структура ExchangeRate содержит вычисляемое свойство floatRate для возврата значения Float.

struct Root : Decodable {
    let exchangeRate : ExchangeRate
    private enum  CodingKeys: String, CodingKey { case exchangeRate = "Realtime Currency Exchange Rate" }
}

struct ExchangeRate : Decodable {

    let fromCode, fromName, toCode, toName, rate : String
    let lastRefreshed, timeZone : String

    private enum  CodingKeys: String, CodingKey {
        case fromCode = "1. From_Currency Code"
        case fromName = "2. From_Currency Name"
        case toCode = "3. To_Currency Code"
        case toName = "4. To_Currency Name"
        case rate = "5. Exchange Rate"
        case lastRefreshed = "6. Last Refreshed"
        case timeZone = "7. Time Zone"
    }

    var floatRate : Float {
        return Float(rate) ?? 0.0
    }
}

Затем сделайте функцию загрузки более универсальной, передав в качестве параметров валюту from и to. Эта версия использует JSONDecoder и возвращает экземпляр ExchangeRate или Error.

func downloadRate(from: String, to: String, completionHandler: @escaping (ExchangeRate?, Error?) -> Void) {

    let urlString = "https://www.alphavantage.co/query?function=CURRENCY_EXCHANGE_RATE&from_currency=\(from)&to_currency=\(to)&apikey=NP3M8LL62YJDO0YX"
    let task = URLSession.shared.dataTask(with: URL(string: urlString)!, completionHandler: { data, response, error in
        if error != nil {
            completionHandler(nil, error!)
        } else {
            do {
                let result = try JSONDecoder().decode(Root.self, from: data!)
                completionHandler(result.exchangeRate, nil)
            } catch {
                completionHandler(nil, error)
            }
        }
    })
    task.resume()
}

и использовать его

func usdQUotesRequest() {

  USDClient().downloadRate(from:"USD", to: "EUR") { exchangeRate, error in

    if let exchangeRate = exchangeRate {
        self.usdtoeurquote = exchangeRate.floatRate
        DispatchQueue.main.async {
            self.stopActivityIndicator()
            self.Refresh.isEnabled = true
        }
    } else {
        DispatchQueue.main.async {
            self.displayAlert("Unable to Retrieve Latest Conversion Rates", message: "\(error!)")
            self.stopActivityIndicator()
            self.Refresh.isEnabled = true
        }
    }
}
person vadian    schedule 22.07.2018
comment
Ой ну спасибо! Однако со стороны self.usdtoeurquote = exchangeRate.floarRate я получаю сообщение об ошибке: Невозможно присвоить значение типа «Плавающая?» для ввода '[String: Any]'. Я объявил usdtoeurqutoe как var usdtoeurquote = [String:Any](). Что не так сейчас? - person lemonator21; 23.07.2018
comment
Мой код заменил словарь пользовательской структурой. Если вас интересуют все поля декодированного JSON, объявите usdtoeurquote как ExchangeRate, назначьте экземпляр структуры иначе как Float И назначьте скорость. - person vadian; 23.07.2018
comment
Да, это сработало! Большое спасибо! Я все еще получаю предупреждение для var usdtoeurquote = ExchangeRate.init(fromCode: 1. From_Currency Code, fromName: 2. From_Currency Name, toCode: 3. To_Currency Code, toName: 4. To_Currency Name, rate: 5. Exchange Rate, lastRefreshed : 6. Последнее обновление, часовой пояс: 7. Часовой пояс) как? Float, который преобразуется из «ExchangeRate» в несвязанный тип «Float», всегда терпит неудачу. - person lemonator21; 23.07.2018
comment
Не преобразовывайте ExchangeRate в Float, это два совершенно разных типа. Пожалуйста, используйте именно мой код (self.usdtoeurquote = exchangeRate.floatRate). Структура ExchangeRate имеет вычисляемое свойство floatRate, которое возвращает Float, созданное из декодированной строки rate. - person vadian; 23.07.2018