Search code examples
jsonswiftcodablejsondecoder

Best way to handle JSON with occasional string data in numeric field?


I wonder if someone could help me out with the best way to handle a type that should be numeric but occasionally shows as string.

enter image description here

This has only just started happening, the struct that I am using is below, I can't fix this on the server, but obviously the inconsistant data is crashing JSONDecoder

struct CountryInfo: Codable {
    var iso2: String
    var iso3: String
    var _id: Int
    var lat: Double
    var long: Double
    var flag: String
}

Any help would be much appreciated.

EDIT: sample.json ADDED

Notice in China _id = 156 but in Iran _id = "NO DATA"

[{"country":"China","countryInfo":{"iso2":"CN","iso3":"CHN","_id":156,"lat":35,"long":105,"flag":"https://raw.githubusercontent.com/NovelCOVID/API/master/assets/flags/cn.png"},"cases":81171,"todayCases":78,"deaths":3277,"todayDeaths":7,"recovered":73159,"active":4735,"critical":1573,"casesPerOneMillion":56,"deathsPerOneMillion":2},{"country":"Italy","countryInfo":{"iso2":"IT","iso3":"ITA","_id":380,"lat":42.8333,"long":12.8333,"flag":"https://raw.githubusercontent.com/NovelCOVID/API/master/assets/flags/it.png"},"cases":69176,"todayCases":5249,"deaths":6820,"todayDeaths":743,"recovered":8326,"active":54030,"critical":3393,"casesPerOneMillion":1144,"deathsPerOneMillion":113},{"country":"USA","countryInfo":{"iso2":"NO DATA","iso3":"NO DATA","_id":"NO DATA","lat":0,"long":0,"flag":"https://raw.githubusercontent.com/NovelCOVID/API/master/assets/flags/unknow.png"},"cases":49976,"todayCases":6242,"deaths":634,"todayDeaths":81,"recovered":368,"active":48974,"critical":1175,"casesPerOneMillion":151,"deathsPerOneMillion":2},{"country":"Spain","countryInfo":{"iso2":"ES","iso3":"ESP","_id":724,"lat":40,"long":-4,"flag":"https://raw.githubusercontent.com/NovelCOVID/API/master/assets/flags/es.png"},"cases":39676,"todayCases":4540,"deaths":2800,"todayDeaths":489,"recovered":3794,"active":33082,"critical":2355,"casesPerOneMillion":849,"deathsPerOneMillion":60},{"country":"Germany","countryInfo":{"iso2":"DE","iso3":"DEU","_id":276,"lat":51,"long":9,"flag":"https://raw.githubusercontent.com/NovelCOVID/API/master/assets/flags/de.png"},"cases":32781,"todayCases":3725,"deaths":156,"todayDeaths":33,"recovered":3133,"active":29492,"critical":23,"casesPerOneMillion":391,"deathsPerOneMillion":2},{"country":"Iran","countryInfo":{"iso2":"NO DATA","iso3":"NO DATA","_id":"NO DATA","lat":0,"long":0,"flag":"https://raw.githubusercontent.com/NovelCOVID/API/master/assets/flags/unknow.png"},"cases":24811,"todayCases":1762,"deaths":1934,"todayDeaths":122,"recovered":8913,"active":13964,"critical":0,"casesPerOneMillion":295,"deathsPerOneMillion":23}]


Solution

  • You can write a custom init(from) where you try to decode the _id using try? to make the result optional which means you need to change the declaration of the property to be optional

    struct Country: Decodable {
        let country: String
        let countryInfo: CountryInfo
    }
    struct CountryInfo: Decodable {
        var iso2: String
        var iso3: String
        var id: Int?
        var lat: Double
        var long: Double
        var flag: String
    
        enum CodingKeys: String, CodingKey {
            case iso2, iso3
            case id = "_id"
            case lat, long, flag
        }
    
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            iso2 = try container.decode(String.self, forKey: .iso2)
            iso3 = try container.decode(String.self, forKey: .iso3)
            id = try? container.decode(Int.self, forKey: .id)
            lat = try container.decode(Double.self, forKey: .lat)
            long = try container.decode(Double.self, forKey: .long)
            flag = try container.decode(String.self, forKey: .flag)
        }
    }
    

    I would also opt for making any property optional where the API might return "NO DATA" since I think nil is clearer and easier to handle later on.