Xcode 9 Swift 4 Комплексное JSON-декодирование - PullRequest
0 голосов
/ 25 октября 2018

Я работаю с данными API, которые возвращают данные JSON, которые трудно декодировать.Призыв API для партии котировки акций.Когда вызывается одна кавычка (не пакетная), результат легко декодируется JSON с использованием простой структуры.Однако в пакетном режиме версия с одинарными кавычками сгруппирована еще в два уровня, которые я не могу декодировать.В целях упрощения чтения я просто вставлю начальные фрагменты данных, чтобы проиллюстрировать проблему.

Единичная кавычка JSON:

{"symbol":"AAPL","companyName":"Apple Inc.","primaryExchange":"Nasdaq Global Select",

Итак, это легко... ключ, пары значений от начала, но в пакетном режиме это становится:

{"AAPL":{"quote":{"symbol":"AAPL","companyName":"Apple Inc.","primaryExchange":"Nasdaq Global Select",

, а затем позже в этом же результате будет вторая или третья или более кавычка, например.

}},"FB":{"quote":{"symbol":"FB","companyName":"Facebook Inc.","primaryExchange":"Nasdaq Global Select",

Так что на самом высоком уровне это не ключ, а значение.А второй уровень - это заполнитель типа метаданных для цитаты (потому что вы также можете запросить другие массивы подэлементов, такие как компания, диаграммы и т. Д.). Я не могу думать о том, как обрабатывать внешние группировки, особенно символы акций AAPL и FB.... как внешние элементы.Есть какие-нибудь мысли?

Я пошел по пути JSONSerialization, который создает строку, которую я также не могу получить в пригодной для использования форме.

Для этого я использую:

    let tkrs = "C,DFS"

    var components = URLComponents()
    components.scheme = "https"
    components.host = "api.iextrading.com" 
    components.path = "/1.0/stock/market/batch"
    let queryItemSymbols = URLQueryItem(name: "symbols", value: "\(tkrs)")
    let queryItemTypes = URLQueryItem(name: "types", value: "quote")
    components.queryItems = [queryItemSymbols,queryItemTypes]
    let session = URLSession.shared
    let task = session.dataTask(with: components.url!) {(data, response, error) in
        guard let data = data else { return }
        do {
            let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
            print(json)

, который выдает:

["C": {
quote =     {
    avgTotalVolume = 17386485;
    calculationPrice = tops;
    change = "1.155";
    changePercent = "0.0181";
    close = "63.8";
    closeTime = 1540411451191;
    companyName = "Citigroup Inc.";

, и есть еще данные, но я сокращаю их.

URL-адреса API:

Пример одинарной кавычки:

https://api.iextrading.com/1.0/stock/aapl/quote

Пример пакетной кавычки:

https://api.iextrading.com/1.0/stock/market/batch?symbols=aapl,fb&types=quote

Структура, которую я успешно использовал для одинарных кавычек, работаеткрасиво с простой строкой кода:

let quote = try JSONDecoder().decode(Quote.self,from: data)

где Цитата является структурой:

struct Quote: Decodable {
    let symbol: String
    let companyName: String
    let primaryExchange: String
    let sector: String
    let calculationPrice: String
    let open: Double
    let openTime: Int
    let close: Double
    let closeTime: Int
    let high: Double
    let low: Double
    let latestPrice: Double
    let latestSource: String
    let latestTime: String
    let latestUpdate: Int
    let latestVolume: Double
    let iexRealtimePrice: Double?
    let iexRealtimeSize: Double?
    let iexLastUpdated: Int?
    let delayedPrice: Double
    let delayedPriceTime: Int
    let extendedPrice: Double
    let extendedChange: Double
    let extendedChangePercent: Double
    let extendedPriceTime: Int
    let previousClose: Double
    let change: Double
    let changePercent: Double
    let iexMarketPercent: Double?
    let iexVolume: Double?
    let avgTotalVolume: Double
    let iexBidPrice: Double?
    let iexBidSize: Double?
    let iexAskPrice: Double?
    let iexAskSize: Double?
    let marketCap: Double
    let peRatio: Double?
    let week52High: Double
    let week52Low: Double
    let ytdChange: Double
}

Редактировать: на основе предоставленного ответа

Работая на игровой площадке, это работаетхорошо с пакетными данными:

func getPrices(){
    let tkrs = "AAPL,FB,C,DFS,MSFT,ATVI"
    var components = URLComponents()
    components.scheme = "https"
    components.host = "api.iextrading.com" ///1.0/stock/market/batch
    components.path = "/1.0/stock/market/batch"
    let queryItemSymbols = URLQueryItem(name: "symbols", value: "\(tkrs)")
    let queryItemTypes = URLQueryItem(name: "types", value: "quote")
    components.queryItems = [queryItemSymbols,queryItemTypes]
    let data = try! Data(contentsOf: components.url!)
    do {
        let response = try JSONDecoder().decode([String:[String: Quote]].self,from: data)
        let tickers = ["AAPL","FB","C","DFS","MSFT","ATVI"]
        for tk in tickers {
            let quote = response[tk]
            let price = quote!["quote"]
            print("\(price!.symbol) \(price!.latestPrice)")
        }
    } catch let jsonErr { print("Error decoding json:",jsonErr)}
}

Но это решает мою первоначальную проблему получения ответа от URLSession только для одной цитаты.Теперь я могу просматривать массив символов акций и обновлять последнюю цену для каждого элемента с помощью этой функции.

func getPrice(ticker: String) -> Double {
    var price = 0.0
    let urlString = "https://api.iextrading.com/1.0/stock/\(ticker)/quote"
    let data = try! Data(contentsOf: URL(string: urlString)!)
    do {
        let response = try JSONDecoder().decode(Quote.self,from: data)
        price = response.latestPrice
    } catch let jsonErr { print("Error decoding JSON:",jsonErr)}
    return price
}

Итак, я перебираю массив открытых сделок с акциями и устанавливаю цену следующим образом...

opentrades[rn].trCurPrice = getPrice(ticker: opentrades[rn].trTicker)

И это прекрасно работает в моем приложении.Хотя я немного волнуюсь о том, как это будет работать во время высокой задержки.Я осознаю, что мне нужен некоторый контроль ошибок, и буду работать над тем, чтобы интегрировать это в будущее.

Редактирование / обновление: на основе отзыва используется подход, который я использую.

Создан класс, который будетделегат, который принимает массив открытых сделок и обновляет цены.

import Foundation

protocol BatchQuoteManagerDelegate {
    func didLoadBatchQuote()
}

class BatchQuoteManager {
    var openPositions = [OpenTradeDur]()
    var delegate: BatchQuoteManagerDelegate? = nil
    func getBatchQuote(tickers: [OpenTradeDur]) {
        var tkrs = ""
        for tk in tickers {
            tkrs = tkrs + "\(tk.trTicker),"
        }
        var components = URLComponents()
        components.scheme = "https"
        components.host = "api.iextrading.com"
        components.path = "/1.0/stock/market/batch"
        let queryItemSymbols = URLQueryItem(name: "symbols", value: "\(tkrs)")
        let queryItemTypes = URLQueryItem(name: "types", value: "quote")
        components.queryItems = [queryItemSymbols,queryItemTypes]
        let session = URLSession.shared
        let task = session.dataTask(with: components.url!) {(data,response,error) in
            guard let data = data, error == nil else { return }
            let response = try! JSONDecoder().decode([String:[String: Quote]].self,from: data)
            for i in 0..<tickers.count {
                let quote = response[tickers[i].trTicker]
                let price = quote!["quote"]
                tickers[i].trCurPrice = price!.latestPrice
            }
            self.openPositions = tickers
            if let delegate = self.delegate {
                DispatchQueue.main.async {
                    delegate.didLoadBatchQuote()
                }
            }
        }
        task.resume()
    }
}

Затем я расширяю свой ViewController с помощью BatchQuoteManagerDelegate, реализую метод func didLoadBatchQuote(), где я получаю обновленные цены через массив BatchQuoteManager.openPositions,Мне просто нужно было определить let batchQuoteManager = BatchQuoteManager() в моем ViewController и в viewDidLoad() включить оператор batchQuoteManager.delegate = self.Как только я узнаю, что все необходимые данные загружены в мой ViewController, я вызываю функцию, чтобы получить цены (в конце viewDidLoad()) с batchQuoteManager.getBatchQuote(tickers: opentrades)

И это все.Пока это работает очень хорошо.

1 Ответ

0 голосов
/ 25 октября 2018

Тип Dictionary условно соответствует Decodable, если связанные с ним KeyType и ValueType соответствуют Decodable.Вы можете расшифровать все Dictionary.

let response = try JSONDecoder().decode([String:[String: Quote]].self,from: data)
let apple = response["AAPL"]
let appleQuote = apple["quote"]

Попробуйте эту суть на игровой площадке

https://gist.github.com/caquant/eeee66b7b8df447c4ea06b8ab8c1116a

Редактировать: Вот краткий пример с URLSession

let session = URLSession.shared
let dataTask = session.dataTask(with: url) { (data, response, error) in
    guard  let data = data, error == nil else { return }
    let response = try! JSONDecoder().decode([String:[String: Quote]].self,from: data)
    let apple = response["FB"]
    let appleQuote = apple!["quote"]
    print(appleQuote!)
}

dataTask.resume()

Примечание. Суть также была обновлена.

...