简体   繁体   English

Swift 解码 JSON 中的唯一键或忽略某些键

[英]Swift Decode unique keys in JSON or ignore certain keys

I'm practicing my Swift skills, this time I'm trying to make a Covid-19 tracker, and for this I found this API , the thing is, that the format retrieved by /cases is something like this (changing keys to make it more readable)我正在练习我的 Swift 技能,这次我正在尝试制作一个 Covid-19 跟踪器,为此我找到了这个API ,问题是/cases检索到的格式是这样的(更改密钥以使它更具可读性)

{
    "Country1": {
        "All": {
            "property1": 0,
            "property2": "foo"
        }
    }, {
        "All": {
            "property1": "0",
            "property2": "bar",
        },
        "State1": {
            "property1": 0,
            "property3": "foobar"
        }
    }
}

And I made the following structs to decode it:我做了以下结构来解码它:

Country国家

struct Country: Codable {
    let All: [String: All]
    
    private enum CodingKeys: String, CodingKey {
        case All
    }
}

All全部

struct All: Codable {
    let confirmed: Int?
    let recovered: Int?
    let deaths: Int?
    let country: String?
    let population: Int?
    let squareKmArea: Int?
    let lifeExpectancy: String?
    var elevationInMeters: String?
    let continent: String?
    let location: String?
    let iso: String?
    let capitalCity: String?
    let lat: String?
    let long: String?
    let updated: String?
    
    private enum CodingKeys: String, CodingKey {
        case confirmed
        case recovered
        case deaths
        case country
        case population
        case squareKmArea = "sq_km_area"
        case lifeExpectancy = "life_expectancy"
        case elevationInMeters = "elevation_in_meters"
        case continent
        case location
        case iso
        case capitalCity = "capital_city"
        case lat
        case long
        case updated
    }
    
    init(confirmed: Int?, recovered: Int?, deaths: Int?, country: String?, population: Int?,
         squareKmArea: Int?, lifeExpectancy: String?, elevationInMeters: String?,
         continent: String?, location: String?, iso: String?, capitalCity: String?,
         lat: String?, long: String?, updated: String?) {
        self.confirmed = confirmed
        self.recovered = recovered
        self.deaths = deaths
        self.country = country
        self.population = population
        self.squareKmArea = squareKmArea
        self.lifeExpectancy = lifeExpectancy
        self.elevationInMeters = elevationInMeters
        self.continent = continent
        self.location = location
        self.iso = iso
        self.capitalCity = capitalCity
        self.lat = lat
        self.long = long
        self.updated = updated
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        self.confirmed = try container.decodeIfPresent(Int.self, forKey: .confirmed)
        self.recovered = try container.decodeIfPresent(Int.self, forKey: .recovered)
        self.deaths = try container.decodeIfPresent(Int.self, forKey: .deaths)
        self.country = try container.decodeIfPresent(String.self, forKey: .country)
        self.population = try container.decodeIfPresent(Int.self, forKey: .population)
        self.squareKmArea = try container.decodeIfPresent(Int.self, forKey: .squareKmArea)
        self.lifeExpectancy = try container.decodeIfPresent(String.self, forKey: .lifeExpectancy)
        self.elevationInMeters = try container.decodeIfPresent(String.self, forKey: .elevationInMeters)
        self.continent = try container.decodeIfPresent(String.self, forKey: .continent)
        self.location = try container.decodeIfPresent(String.self, forKey: .location)
        self.iso = try container.decodeIfPresent(String.self, forKey: .iso)
        self.capitalCity = try container.decodeIfPresent(String.self, forKey: .capitalCity)
        self.lat = try container.decodeIfPresent(String.self, forKey: .lat)
        self.long = try container.decodeIfPresent(String.self, forKey: .long)
        self.updated = try container.decodeIfPresent(String.self, forKey: .updated)
        
        do {
            self.elevationInMeters = try String(container.decodeIfPresent(Int.self, forKey: .elevationInMeters) ?? 0)
        } catch DecodingError.typeMismatch {
            print("Not a number")
            self.elevationInMeters = try container.decodeIfPresent(String.self, forKey: .elevationInMeters) ?? ""
        }
    }
}

The elevation in meters can come in either String or Int value (4+ digits long > String (appends a comma), otherwise Int) (Idea taken from this answer以米为单位的海拔可以采用字符串或整数值(4+ 位长 > 字符串(附加逗号),否则为整数)(取自此答案的想法

And I try to decode it this way我试着用这种方式解码

if let decodedData = self.jsonUtilities.decode(json: safeData, as: [String : Country].self) {
    print(decodedData)
}

My above decode method looks like this:我上面的decode方法是这样的:

func decode<T: Decodable>(json: Data, as clazz: T.Type) -> T? {
    do {
        let decoder = JSONDecoder()
        let data = try decoder.decode(T.self, from: json)
        
        return data
    } catch {
        print(error)
        print("An error occurred while parsing JSON")
    }
    
    return nil
}

I found this answer where they suggest adding all the Coding Keys there, but these are a ton.我在他们建议在那里添加所有编码键的地方找到了这个答案,但这些都很多。

I haven't added all the keys, as it's an 8k+ lines JSON, so I was wondering if there's an easier way to decode it, as I can't think of a better way to decode this JSON with unique keys.我没有添加所有的密钥,因为它是一个 8k+ 行 JSON,所以我想知道是否有更简单的方法来解码它,因为我想不出更好的方法来解码这个具有唯一密钥的 JSON。

Alternatively if I could ignore all the keys that aren't "All" might also work, as I'm just trying to get the totals per country and their locations to place them in a map.或者,如果我可以忽略所有不是"All"的键也可以工作,因为我只是想获得每个国家的总数及其位置,以将它们放在 map 中。

So far I get this error:到目前为止,我收到此错误:

typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Diamond Princess", intValue: nil), CodingKeys(stringValue: "All", intValue: nil), _JSONKey(stringValue: "recovered", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, Any> but found a number instead.", underlyingError: nil))
An error occurred while parsing JSON

And afaik it's because it's not finding the key "Diamond Princess" which is a state (or so I believe) in Canada (according to my JSON), because I haven't added it for the reasons above.而且afaik是因为它没有找到关键的“钻石公主”,它是加拿大的state(或者我相信)(根据我的JSON),因为我没有因为上述原因添加它。

When you have dynamic keys, you could decode it as [String: ...] dictionary.当您有动态键时,您可以将其解码为[String: ...]字典。

The structure of the JSON is as follows: JSON的结构如下:

{
  "Canada": {
     "All": { ... },
     "Ontario": { ... },
    ...
  },
  "Mexico": { ... },
   ...
}

All the country stats have the key "All" and if that's all you need then you could create the following structs:所有国家/地区的统计数据都有键"All" ,如果这就是您所需要的,那么您可以创建以下结构:

struct Country: Decodable {
    var all: CountryAll
    enum CodingKeys: String, CodingKey {
        case all = "All"
    }
}

struct CountryAll: Decodable {
    var confirmed: Int
    var recovered: Int
    //.. etc
}

and decode as:并解码为:

let countries = try JSONDecoder().decode([String: Country].self, from: json)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM