0

Let's say we have this URL, I'm using CodableAlamofire to fetch and parse the JSON response.

For the above URL response I've created following Codable classes.

struct CoinList: Codable {
    let raw: Raw
    let display: Display

    enum CodingKeys: String, CodingKey {
        case raw = "RAW"
        case display = "DISPLAY"
    }
}

struct Display: Codable {
    let usd: [String: Usd]

    enum CodingKeys: String, CodingKey {
        case usd = "USD"
    }
}

struct Raw: Codable {
    let usd: [String: Usd]

    enum CodingKeys: String, CodingKey {
        case usd = "USD"
    }
}

struct Usd: Codable {
    let type, market, fromsymbol, tosymbol: String
    let flags: String
    let price: Double
    let lastupdate: Int
    let lastvolume, lastvolumeto: Double
    let lasttradeid: String
    let volumeday, volumedayto, volume24Hour, volume24Hourto: Double
    let openday, highday, lowday, open24Hour: Double
    let high24Hour, low24Hour: Double
    let lastmarket: String
    let change24Hour, changepct24Hour, changeday, changepctday: Double
    let supply, mktcap, totalvolume24H, totalvolume24Hto: Double

    enum CodingKeys: String, CodingKey {
        case type = "TYPE"
        case market = "MARKET"
        case fromsymbol = "FROMSYMBOL"
        case tosymbol = "TOSYMBOL"
        case flags = "FLAGS"
        case price = "PRICE"
        case lastupdate = "LASTUPDATE"
        case lastvolume = "LASTVOLUME"
        case lastvolumeto = "LASTVOLUMETO"
        case lasttradeid = "LASTTRADEID"
        case volumeday = "VOLUMEDAY"
        case volumedayto = "VOLUMEDAYTO"
        case volume24Hour = "VOLUME24HOUR"
        case volume24Hourto = "VOLUME24HOURTO"
        case openday = "OPENDAY"
        case highday = "HIGHDAY"
        case lowday = "LOWDAY"
        case open24Hour = "OPEN24HOUR"
        case high24Hour = "HIGH24HOUR"
        case low24Hour = "LOW24HOUR"
        case lastmarket = "LASTMARKET"
        case change24Hour = "CHANGE24HOUR"
        case changepct24Hour = "CHANGEPCT24HOUR"
        case changeday = "CHANGEDAY"
        case changepctday = "CHANGEPCTDAY"
        case supply = "SUPPLY"
        case mktcap = "MKTCAP"
        case totalvolume24H = "TOTALVOLUME24H"
        case totalvolume24Hto = "TOTALVOLUME24HTO"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)

        type = try values.decode(String.self, forKey: .type)
        market = try values.decode(String.self, forKey: .market)
        fromsymbol = try values.decode(String.self, forKey: .fromsymbol)
        tosymbol = try values.decode(String.self, forKey: .tosymbol)
        flags = try values.decode(String.self, forKey: .flags)

        price = try values.decode(Double.self, forKey: .price)
        lastvolume = try values.decode(Double.self, forKey: .lastvolume)
        lastvolumeto = try values.decode(Double.self, forKey: .lastvolumeto)
        lastupdate = try values.decode(Int.self, forKey: .lastupdate)

        if let value = try? values.decode(Int.self, forKey: .lasttradeid) {
            lasttradeid = String(value)
        } else {
            lasttradeid = try values.decode(String.self, forKey: .lasttradeid)
        }

        volumeday = try values.decode(Double.self, forKey: .volumeday)
        volumedayto = try values.decode(Double.self, forKey: .volumedayto)
        volume24Hour = try values.decode(Double.self, forKey: .volume24Hour)
        volume24Hourto = try values.decode(Double.self, forKey: .volume24Hourto)
        openday = try values.decode(Double.self, forKey: .openday)
        highday = try values.decode(Double.self, forKey: .highday)
        lowday = try values.decode(Double.self, forKey: .lowday)
        open24Hour = try values.decode(Double.self, forKey: .open24Hour)
        high24Hour = try values.decode(Double.self, forKey: .high24Hour)
        low24Hour = try values.decode(Double.self, forKey: .low24Hour)

        lastmarket = try values.decode(String.self, forKey: .lastmarket)

        change24Hour = try values.decode(Double.self, forKey: .change24Hour)
        changepct24Hour = try values.decode(Double.self, forKey: .changepct24Hour)
        changeday = try values.decode(Double.self, forKey: .changeday)
        changepctday = try values.decode(Double.self, forKey: .changepctday)

        supply = try values.decode(Double.self, forKey: .supply)
        mktcap = try values.decode(Double.self, forKey: .mktcap)
        totalvolume24H = try values.decode(Double.self, forKey: .totalvolume24H)
        totalvolume24Hto = try values.decode(Double.self, forKey: .totalvolume24Hto)
    }
}

After successful response, I'm unable to parse the JSON, I studied a lot on nested JSON parsing with Swift Codable but still unable to get success.

Please help me to parse the above JSON response with nested JSON structure, like say Display and Raw object have all properties of Usd.

I think there is some minor mistake I'm doing.

Any help will be appreciated.

UPDATE

I've created JSON file for the response and parsing it,

if let path = Bundle.main.path(forResource: "test", ofType: "json") {
    do {
        let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
        let result = try JSONDecoder().decode(CoinList.self, from: data)
        print(result)
    } catch {
        print(error.localizedDescription)
    }
}

Error is this,

enter image description here

JSON structure is like this,

enter image description here

Please find my my web api call,

    Alamofire.request(TickerRouter.PriceMultiFull(params: params))
        .validate(statusCode: 200..<300)
        .responseString { responseData in                
            let data = responseData.value?.replacingOccurrences(of: "\\/", with: "/").data(using: .utf8)
            if responseData.error == nil {
                let decoder = JSONDecoder()
                decoder.keyDecodingStrategy = .custom({ AnyKey(stringValue: $0.last!.stringValue.lowercased())!})
                decoder.dateDecodingStrategy = .secondsSince1970
                let result = try? decoder.decode(CoinList.self, from: data!)
                success(result!)
            } else {
                let msg = "Something went wrong. Please try again later"
                failure(msg)
            }
    }

result is nil here, it is working with local json file. :(

6
  • You can save yourself so much time writing boilerplate code by just using a custom KeyDecodingStrategy to make your key become uppercase Commented Jun 13, 2018 at 5:37
  • What problem are you having exactly? Commented Jun 13, 2018 at 5:38
  • @rmaddy I've created json file for the following response, Please find my updated answer for parsing json file, error is screenshot attached in question. please find it. Commented Jun 13, 2018 at 5:44
  • The second and third lines of the struct thing don't look correct. Commented Jun 13, 2018 at 5:55
  • @ElTomato can you please elaborate more? Commented Jun 13, 2018 at 5:56

1 Answer 1

1

At first glance you will notice that the member types in the USD dictionary for raw and display are widely different, so a single struct for both doesn't work.

The root object is (the String keys are the BTH and XRP symbols)

struct CoinList: Codable {
    let raw: [String: Raw]
    let display: [String: Display]
}

The Raw and Display structs contain the usd key and the appropriate struct

struct Raw: Codable {
    let usd: USDRaw
}

struct Display: Codable {
    let usd: USDDisplay
}

The USDRaw and USDDisplay structs contain all data, lastupdate in USDRaw will be decoded as Date

struct USDRaw: Codable {
    let type, market, flags, fromsymbol, tosymbol: String
    let price : Double
    let lastupdate: Date
    let lastvolume, lastvolumeto: Double
    let lasttradeid: String
    let volumeday, volumedayto, volume24hour, volume24hourto: Double
    let openday, highday, lowday, open24hour: Double
    let high24hour, low24hour: Double
    let lastmarket: String
    let change24hour, changepct24hour, changeday, changepctday: Double
    let supply, mktcap, totalvolume24h, totalvolume24hto: Double

}

struct USDDisplay: Codable {
    let fromsymbol, tosymbol, market, price, lastupdate: String
    let lastvolume, lastvolumeto, lasttradeid, volumeday, volumedayto, volume24hour, volume24hourto : String
    let openday, highday, lowday, open24hour, high24hour, low24hour, lastmarket: String
    let change24hour, changepct24hour, changeday, changepctday: String
    let supply, mktcap, totalvolume24h, totalvolume24hto: String
}

To get rid of specifying all CodingKeys and make the keys lowercased create a helper struct (stolen from the documentation)

struct AnyKey: CodingKey {
    var stringValue: String
    var intValue: Int?

    init?(stringValue: String) {
        self.stringValue = stringValue
        self.intValue = nil
    }

    init?(intValue: Int) {
        self.stringValue = String(intValue)
        self.intValue = intValue
    }
}

Pass the custom keyDecodingStrategy and a suitable dateDecodingStrategy to the decoder

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ AnyKey(stringValue: $0.last!.stringValue.lowercased())!}) 
decoder.dateDecodingStrategy = .secondsSince1970
let coinList = try decoder.decode(CoinList.self, from: data)
print(coinList)
Sign up to request clarification or add additional context in comments.

6 Comments

Thank you so much, can you please tell how to overcome with the following error ""Expected to decode String but found a number instead." in lasttradeid? and one more thing it is working with the test.json but not from the response from the server
In the given URL all lasttradeid values are String. But if this could happen unfortunately you have to add the huge init(from decoder method to handle the cases.
can you please tell why it is not working with the server response?
I'm not familiar with Alamofire. Is the line to remove backslashes really necessary? I'd try responseData directly.
same problem if I remove the line :(
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.