PPL
PPL

Reputation: 6565

JSON Parsing, issue with Nested JSON structure

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. :(

Upvotes: 0

Views: 206

Answers (1)

vadian
vadian

Reputation: 285270

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)

Upvotes: 1

Related Questions