Search code examples
jsonswiftjsonconvert

Problem while converting JSON to Swift "The data couldn’t be read because it isn’t in the correct format."


When I am converting JSON from coinapi.com (https://rest-sandbox.coinapi.io/v1/assets/?apikey=72869C8A-D49B-4DC5-9A3B-17D9804AEE97) I am getting a problem "The data couldn’t be read because it isn’t in the correct format".

As result at the end after converting I have:

struct CryptListStruct: Codable {

    let assetID, name: String
    let typeIsCrypto: Int
    let dataQuoteStart, dataQuoteEnd, dataOrderbookStart, dataOrderbookEnd: String
    let dataTradeStart, dataTradeEnd: String
    let dataSymbolsCount: Int
    let volume1HrsUsd, volume1DayUsd: Double
    let volume1MthUsd: Int
    let priceUsd: Double?
    let idIcon, dataStart, dataEnd: String

    enum CodingKeys: String, CodingKey {
        case assetID = "asset_id"
        case name
        case typeIsCrypto = "type_is_crypto"
        case dataQuoteStart = "data_quote_start"
        case dataQuoteEnd = "data_quote_end"
        case dataOrderbookStart = "data_orderbook_start"
        case dataOrderbookEnd = "data_orderbook_end"
        case dataTradeStart = "data_trade_start"
        case dataTradeEnd = "data_trade_end"
        case dataSymbolsCount = "data_symbols_count"
        case volume1HrsUsd = "volume_1hrs_usd"
        case volume1DayUsd = "volume_1day_usd"
        case volume1MthUsd = "volume_1mth_usd"
        case priceUsd = "price_usd"
        case idIcon = "id_icon"
        case dataStart = "data_start"
        case dataEnd = "data_end"
    }
}

I think it is because of a lot of data there and they are not the same. How can I get the correct model?

That's how my function looks like:

    
    
    func getCryptList(_ completion: @escaping (CryptListStruct) -> Void, _ error: @escaping (String) -> Void){
    let header: [String: String] = [:]
       self.get(url: "https://rest.coinapi.io/v1/assets/?apikey=367FB27A-371B-4DBD-AB81-E98AAFE857B2", header: header, completion: {
            (data) in
            do {
                guard let data = data else {return}
                let crpytList = try JSONDecoder().decode(CryptListStruct.self, from: data)
                DispatchQueue.main.async {
                    completion(crpytList)
                }
            } catch let err {
                       error(err.localizedDescription)
                   }
               }, error: error)
    }
``

Solution

  • First of all

    never print(error.localizedDescription).

    in a JSONDecoder catch block. You get a generic but quite meaningless error message.

    Always print the entire error, DecodingErrors are very descriptive

    print(error)
    

    Your code contains three major errors, one of them (error #3) occurs multiple times

    Error #1

    typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))

    indicates that the root object is an array, the JSON starts clearly with [

    Solution: Decode [CryptListStruct].self


    Error #2

    dataCorrupted(Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "volume_1mth_usd", intValue: nil)], debugDescription: "Parsed JSON number <3699822674922524.74> does not fit in Int.", underlyingError: nil))

    indicates that the received value 3699822674922524.74 is actually a Double.

    Solution: Declare

    let volume1MthUsd: Double
    

    Error #3

    keyNotFound(CodingKeys(stringValue: "id_icon", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 4", intValue: 4)], debugDescription: "No value associated with key CodingKeys(stringValue: "id_icon", intValue: nil) ("id_icon").", underlyingError: nil))

    indicates that the key id_icon is missing (at least) in the 5th item of the array.

    Solution: Declare the type as optional

    let idIcon : String?
    

    The same error occurs for dataTradeStart, dataTradeEnd, dataQuoteStart, dataQuoteEnd, dataOrderbookStart, dataOrderbookEnd, dataStart, dataEnd

    let dataQuoteStart, dataQuoteEnd, dataOrderbookStart, dataOrderbookEnd: String?
    let dataTradeStart, dataTradeEnd : String?
    let dataStart, dataEnd: String?
    

    Side note:

    You can delete the entire CodingKeys enum if you replace assetID with assetId and add the convertFromSnakeCase key decoding strategy

    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let crpytList = try decoder.decode([CryptListStruct].self, from: data)