Expected to decode Array<Any> but found a dictionary instead
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)
![Wizzardzz](https://i.stack.imgur.com/5XY7u.png?s=256&g=1)
Wizzardzz
you should try something that seems impossible to achieve sometimes
Updated on August 04, 2022Comments
-
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 over 6 yearsThank you very much Vadian! This is exactly what I was looking for. Is there a simpler syntax for
result.raw.eth.usd.percentChange24h
? -
vadian over 6 yearsYou could declare a computed variable in
Root
to getraw.eth.usd
-
Wizzardzz over 6 yearsGreat, by the way I am getting
Initializer for conditional binding must have Optional type, not 'Root'
on thelet result
line. I'd like to keep my do-catch method so how can I get rid of the error? -
vadian over 6 yearsDon't
if let
, if the decoding succeeds the result is a non-optional -
Leo Dabus over 6 yearsDon't forget to tell OP to call completion inside the catch and inside the guard else bracket
completion(nil, error)