Expected to decode Array<Any> but found a dictionary instead

23,535

First of all the JSON does not contain any array. It's very very easy to read JSON. There are only 2 (two!) collection types, array [] and dictionary {}. As you can see there are no square brackets at all in the JSON string.

Any (sub)dictionary {} has to be decoded to its own type, so it's supposed to be

struct Root : Decodable {
    private enum CodingKeys : String, CodingKey { case raw = "RAW" }
    let raw : RAW
}

struct RAW : Decodable {
    private enum CodingKeys : String, CodingKey { case eth = "ETH" }
    let eth : ETH
}

struct ETH : Decodable {
    private enum CodingKeys : String, CodingKey { case usd = "USD" }
    let usd : USD
}

struct USD : Decodable {

    private enum CodingKeys : String, CodingKey {
        case type = "TYPE"
        case market = "MARKET"
        case price = "PRICE"
        case percentChange24h = "CHANGEPCT24HOUR"
    }
    let type : String
    let market : String
    let price : Double
    let percentChange24h : Double
}

To decode the JSON and and print percentChange24h you have to write

 let result = try JSONDecoder().decode(Root.self, from: data)
 print("percentChange24h", result.raw.eth.usd.percentChange24h)
Share:
23,535
Wizzardzz
Author by

Wizzardzz

you should try something that seems impossible to achieve sometimes

Updated on August 04, 2022

Comments

  • Wizzardzz
    Wizzardzz almost 2 years

    I was fetching data from an API returning an array but needed to replace it by an API that has "sub levels":

    RAW:
        ETH:
            USD:
                 TYPE:              "5"
                 MARKET:            "CCCAGG"
                 FROMSYMBOL:        "ETH"
                 TOSYMBOL:          "USD"
                 PRICE:             680.89
                 CHANGEPCT24HOUR    :   -9.313816893529749
    

    Here is my struct:

    struct Ethereum: Codable {
    
        let percentChange24h: String
        let priceUSD: String
    
        private enum CodingKeys: String, CodingKey {
            case priceUSD = "PRICE", percentChange24h = "CHANGEPCT24HOUR"
        }
    }
    

    And the implementation:

        func fetchEthereumInfo(completion: @escaping (Ethereum?, Error?) -> Void) {
        let url = URL(string: "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD")!
    
        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
            guard let data = data else { return }
            do {
                if let ethereumUSD = try JSONDecoder().decode([Ethereum].self, from: data).first {
                    print(ethereumUSD)
                    completion(ethereumUSD, nil)
                }
            } catch {
                print(error)
            }
        }
        task.resume()
    }
    

    The console prints typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))

    I can't really figure out what to update in my code or what this form of API is

  • Wizzardzz
    Wizzardzz over 6 years
    Thank you very much Vadian! This is exactly what I was looking for. Is there a simpler syntax for result.raw.eth.usd.percentChange24h ?
  • vadian
    vadian over 6 years
    You could declare a computed variable in Root to get raw.eth.usd
  • Wizzardzz
    Wizzardzz over 6 years
    Great, by the way I am getting Initializer for conditional binding must have Optional type, not 'Root' on the let result line. I'd like to keep my do-catch method so how can I get rid of the error?
  • vadian
    vadian over 6 years
    Don't if let, if the decoding succeeds the result is a non-optional
  • Leo Dabus
    Leo Dabus over 6 years
    Don't forget to tell OP to call completion inside the catch and inside the guard else bracket completion(nil, error)