我正在使用Swift 4并尝试解析一些JSON数据,这些数据显然在某些情况下对于同一键可能具有不同的类型值,例如:
{ "type": 0.0 }
和
{ "type": "12.44591406" }
我实际上坚持定义我的名字,struct因为我不知道如何处理这种情况,因为
struct
struct ItemRaw: Codable { let parentType: String enum CodingKeys: String, CodingKey { case parentType = "type" } }
抛出"Expected to decode String but found a number instead.",自然,
"Expected to decode String but found a number instead."
struct ItemRaw: Codable { let parentType: Float enum CodingKeys: String, CodingKey { case parentType = "type" } }
相应地抛出"Expected to decode Float but found a string/data instead."。
"Expected to decode Float but found a string/data instead."
定义我时如何处理这种(和类似的)情况struct?
尝试对Reddit列表JSON响应上的“已编辑”字段进行解码/编码时遇到了相同的问题。我创建了一个结构,该结构表示给定键可能存在的动态类型。键可以是布尔值或整数。
{ "edited": false } { "edited": 123456 }
如果只需要能够解码,则只需实现init(from :)。如果您需要同时使用两种方法,则需要实现encode(to :)函数。
struct Edited: Codable { let isEdited: Bool let editedTime: Int // Where we determine what type the value is init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() // Check for a boolean do { isEdited = try container.decode(Bool.self) editedTime = 0 } catch { // Check for an integer editedTime = try container.decode(Int.self) isEdited = true } } // We need to go back to a dynamic type, so based on the data we have stored, encode to the proper type func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try isEdited ? container.encode(editedTime) : container.encode(false) } }
在我的Codable类中,然后使用我的结构。
struct Listing: Codable { let edited: Edited }
编辑:针对您的方案的更具体的解决方案
我建议在解码时使用CodingKey协议和一个枚举来存储所有属性。当您创建符合Codable的内容时,编译器将为您创建一个私有枚举CodingKeys。这使您可以根据JSON Object属性键决定要做什么。
仅举例来说,这就是我正在解码的JSON:
{"type": "1.234"} {"type": 1.234}
如果您只想将double值从字符串转换为Double,则只需解码字符串,然后从中创建一个double。(这是Itai Ferber所做的,然后您还必须使用trycoder.decode(type:forKey :)对所有属性进行解码)
struct JSONObjectCasted: Codable { let type: Double? init(from decoder: Decoder) throws { // Decode all fields and store them let container = try decoder.container(keyedBy: CodingKeys.self) // The compiler creates coding keys for each property, so as long as the keys are the same as the property names, we don't need to define our own enum. // First check for a Double do { type = try container.decode(Double.self, forKey: .type) } catch { // The check for a String and then cast it, this will throw if decoding fails if let typeValue = Double(try container.decode(String.self, forKey: .type)) { type = typeValue } else { // You may want to throw here if you don't want to default the value(in the case that it you can't have an optional). type = nil } } // Perform other decoding for other properties. } }
如果需要将类型和值一起存储,则可以使用符合Codable的枚举而不是struct。然后,您可以仅使用具有JSONObjectCustomEnum的“ type”属性的switch语句,并根据大小写执行操作。
struct JSONObjectCustomEnum: Codable { let type: DynamicJSONProperty } // Where I can represent all the types that the JSON property can be. enum DynamicJSONProperty: Codable { case double(Double) case string(String) init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() // Decode the double do { let doubleVal = try container.decode(Double.self) self = .double(doubleVal) } catch DecodingError.typeMismatch { // Decode the string let stringVal = try container.decode(String.self) self = .string(stringVal) } } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { case .double(let value): try container.encode(value) case .string(let value): try container.encode(value) } } }