如何在Codable类型中使用Any
我目前正在处理项目中的Codable
类型并遇到问题。
struct Person: Codable { var id: Any }
上面代码中的id
可以是String
或Int
。 这是id
为Any
类型的原因。
我知道Any
不是Codable
。
我需要知道的是我如何才能使它发挥作用。
Codable需要知道要转换的类型。
首先,我会尝试解决不知道类型的问题,看看你是否可以修复它并使其更简单。
否则,我现在想到解决问题的唯一方法就是使用如下的generics。
struct Person { var id: T var name: String } let person1 = Person(id: 1, name: "John") let person2 = Person(id: "two", name: "Steve")
量子价值
首先,您可以定义一个可以从String
和Int
值解码的类型。 这里是。
enum QuantumValue: Decodable { case int(Int), string(String) init(from decoder: Decoder) throws { if let int = try? decoder.singleValueContainer().decode(Int.self) { self = .int(int) return } if let string = try? decoder.singleValueContainer().decode(String.self) { self = .string(string) return } throw QuantumError.missingValue } enum QuantumError:Error { case missingValue } }
人
现在您可以像这样定义结构
struct Person: Decodable { let id: QuantumValue }
而已。 我们来试试吧!
JSON 1: id
是String
let data = """ { "id": "123" } """.data(using: String.Encoding.utf8)! if let person = try? JSONDecoder().decode(Person.self, from: data) { print(person) }
JSON 2: id
是Int
let data = """ { "id": 123 } """.data(using: String.Encoding.utf8)! if let person = try? JSONDecoder().decode(Person.self, from: data) { print(person) }
我解决了这个问题,定义了一个名为AnyDecodable的新Decodable Struct,因此我使用AnyDecodable而不是Any。 它也适用于嵌套类型。
在游乐场试试这个:
var json = """ { "id": 12345, "name": "Giuseppe", "last_name": "Lanza", "age": 31, "happy": true, "rate": 1.5, "classes": ["maths", "phisics"], "dogs": [ { "name": "Gala", "age": 1 }, { "name": "Aria", "age": 3 } ] } """ public struct AnyDecodable: Decodable { public var value: Any private struct CodingKeys: CodingKey { var stringValue: String var intValue: Int? init?(intValue: Int) { self.stringValue = "\(intValue)" self.intValue = intValue } init?(stringValue: String) { self.stringValue = stringValue } } public init(from decoder: Decoder) throws { if let container = try? decoder.container(keyedBy: CodingKeys.self) { var result = [String: Any]() try container.allKeys.forEach { (key) throws in result[key.stringValue] = try container.decode(AnyDecodable.self, forKey: key).value } value = result } else if var container = try? decoder.unkeyedContainer() { var result = [Any]() while !container.isAtEnd { result.append(try container.decode(AnyDecodable.self).value) } value = result } else if let container = try? decoder.singleValueContainer() { if let intVal = try? container.decode(Int.self) { value = intVal } else if let doubleVal = try? container.decode(Double.self) { value = doubleVal } else if let boolVal = try? container.decode(Bool.self) { value = boolVal } else if let stringVal = try? container.decode(String.self) { value = stringVal } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "the container contains nothing serialisable") } } else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not serialise")) } } } let stud = try! JSONDecoder().decode(AnyDecodable.self, from: jsonData).value as! [String: Any] print(stud)
如果您对编码部分感兴趣,可以将我的结构扩展为AnyCodable。
编辑:我实际上做到了。
这是AnyCodable
struct AnyCodable: Decodable { var value: Any struct CodingKeys: CodingKey { var stringValue: String var intValue: Int? init?(intValue: Int) { self.stringValue = "\(intValue)" self.intValue = intValue } init?(stringValue: String) { self.stringValue = stringValue } } init(value: Any) { self.value = value } init(from decoder: Decoder) throws { if let container = try? decoder.container(keyedBy: CodingKeys.self) { var result = [String: Any]() try container.allKeys.forEach { (key) throws in result[key.stringValue] = try container.decode(AnyCodable.self, forKey: key).value } value = result } else if var container = try? decoder.unkeyedContainer() { var result = [Any]() while !container.isAtEnd { result.append(try container.decode(AnyCodable.self).value) } value = result } else if let container = try? decoder.singleValueContainer() { if let intVal = try? container.decode(Int.self) { value = intVal } else if let doubleVal = try? container.decode(Double.self) { value = doubleVal } else if let boolVal = try? container.decode(Bool.self) { value = boolVal } else if let stringVal = try? container.decode(String.self) { value = stringVal } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "the container contains nothing serialisable") } } else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not serialise")) } } } extension AnyCodable: Encodable { func encode(to encoder: Encoder) throws { if let array = value as? [Any] { var container = encoder.unkeyedContainer() for value in array { let decodable = AnyCodable(value: value) try container.encode(decodable) } } else if let dictionary = value as? [String: Any] { var container = encoder.container(keyedBy: CodingKeys.self) for (key, value) in dictionary { let codingKey = CodingKeys(stringValue: key)! let decodable = AnyCodable(value: value) try container.encode(decodable, forKey: codingKey) } } else { var container = encoder.singleValueContainer() if let intVal = value as? Int { try container.encode(intVal) } else if let doubleVal = value as? Double { try container.encode(doubleVal) } else if let boolVal = value as? Bool { try container.encode(boolVal) } else if let stringVal = value as? String { try container.encode(stringVal) } else { throw EncodingError.invalidValue(value, EncodingError.Context.init(codingPath: [], debugDescription: "The value is not encodable")) } } } }
你可以在游乐场用这种方式测试它以前的json:
let stud = try! JSONDecoder().decode(AnyCodable.self, from: jsonData) print(stud.value as! [String: Any]) let backToJson = try! JSONEncoder().encode(stud) let jsonString = String(bytes: backToJson, encoding: .utf8)! print(jsonString)
您可以使用接受Int
或String
的枚举替换Any
:
enum Id: Codable { case numeric(value: Int) case named(name: String) } struct Person: Codable { var id: Id }
然后编译器会抱怨Id
不符合Decodable
。 由于Id
具有关联值,因此您需要自己实现。 请阅读https://littlebitesofcocoa.com/318-codable-enums ,了解如何执行此操作的示例。
如果你的问题是id的类型不确定,因为它可能是字符串或整数值,我可以建议你这篇博文: http : //agostini.tech/2017/11/12/swift-4-codable -in-现实生活部分-2 /
基本上我定义了一个新的Decodable类型
public struct UncertainValue: Decodable { public var tValue: T? public var uValue: U? public var value: Any? { return tValue ?? uValue } public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() tValue = try? container.decode(T.self) uValue = try? container.decode(U.self) if tValue == nil && uValue == nil { //Type mismatch throw DecodingError.typeMismatch(type(of: self), DecodingError.Context(codingPath: [], debugDescription: "The value is not of type \(T.self) and not even \(U.self)")) } } }
从现在开始,您的Person对象将是
struct Person: Decodable { var id: UncertainValue }
您将能够使用id.value访问您的ID
首先,正如您可以阅读其他答案和评论,使用Any
对此并不是一个好的设计。 如果可能的话,再考虑一下。
也就是说,如果你想坚持自己的原因,你应该编写自己的编码/解码,并在序列化的JSON中采用某种约定。
下面的代码通过将id
始终作为字符串编码并根据找到的值解码为Int
或String
来实现它。
import Foundation struct Person: Codable { var id: Any init(id: Any) { self.id = id } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: Keys.self) if let idstr = try container.decodeIfPresent(String.self, forKey: .id) { if let idnum = Int(idstr) { id = idnum } else { id = idstr } return } fatalError() } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: Keys.self) try container.encode(String(describing: id), forKey: .id) } enum Keys: String, CodingKey { case id } } extension Person: CustomStringConvertible { var description: String { return "" } }
例子
用数字id
编码对象:
var p1 = Person(id: 1) print(String(data: try JSONEncoder().encode(p1), encoding: String.Encoding.utf8) ?? "/* ERROR */") // {"id":"1"}
用字符串id
编码对象:
var p2 = Person(id: "root") print(String(data: try JSONEncoder().encode(p2), encoding: String.Encoding.utf8) ?? "/* ERROR */") // {"id":"root"}
解码为数字id
:
print(try JSONDecoder().decode(Person.self, from: "{\"id\": \"2\"}".data(using: String.Encoding.utf8)!)) //
解码为字符串id
:
print(try JSONDecoder().decode(Person.self, from: "{\"id\": \"admin\"}".data(using: String.Encoding.utf8)!)) //
另一种实现方式是对Int
或String
进行编码,并将解码尝试包装在do...catch
。
在编码部分:
if let idstr = id as? String { try container.encode(idstr, forKey: .id) } else if let idnum = id as? Int { try container.encode(idnum, forKey: .id) }
然后在多次尝试中解码为正确的类型:
do { if let idstr = try container.decodeIfPresent(String.self, forKey: .id) { id = idstr id_decoded = true } } catch { /* pass */ } if !id_decoded { do { if let idnum = try container.decodeIfPresent(Int.self, forKey: .id) { id = idnum } } catch { /* pass */ } }
在我看来这很丑陋。
根据您对服务器序列化的控制,您可以使用其中任何一个或编写适合实际序列化的其他内容。
为了使密钥成为任何 ,我喜欢以上所有答案。 但是,如果您不确定服务器人员将发送哪种数据类型,那么您使用Quantum类(如上所述),但Quantum类型很难使用或管理。 所以这是我的解决方案,使您的可解码类密钥作为任何数据类型(或obj-c爱好者的“id”)
class StatusResp:Decodable{ var success:Id? // Here i am not sure which datatype my server guy will send } enum Id: Decodable { case int(Int), double(Double), string(String) // Add more cases if you want init(from decoder: Decoder) throws { //Check each case if let dbl = try? decoder.singleValueContainer().decode(Double.self),dbl.truncatingRemainder(dividingBy: 1) != 0 { // It is double not a int value self = .double(dbl) return } if let int = try? decoder.singleValueContainer().decode(Int.self) { self = .int(int) return } if let string = try? decoder.singleValueContainer().decode(String.self) { self = .string(string) return } throw IdError.missingValue } enum IdError:Error { // If no case matched case missingValue } var any:Any{ get{ switch self { case .double(let value): return value case .int(let value): return value case .string(let value): return value } } } }
用法:
let json = "{\"success\":\"hii\"}".data(using: .utf8) // response will be String //let json = "{\"success\":50.55}".data(using: .utf8) //response will be Double //let json = "{\"success\":50}".data(using: .utf8) //response will be Int let decoded = try? JSONDecoder().decode(StatusResp.self, from: json!) print(decoded?.success) // It will print Any if let doubleValue = decoded?.success as? Double { }else if let doubleValue = decoded?.success as? Int { }else if let doubleValue = decoded?.success as? String { }
Luca Angeletti的解决方案没有涵盖角落案例。
例如,如果Cordinate的类型是Double或[Double],Angeletti的解决方案将导致错误:“预计解码Double但发现了一个数组”
在这种情况下,您必须在Cordinate中使用嵌套枚举。
enum Cordinate: Decodable { case double(Double), array([Cordinate]) init(from decoder: Decoder) throws { if let double = try? decoder.singleValueContainer().decode(Double.self) { self = .double(double) return } if let array = try? decoder.singleValueContainer().decode([Cordinate].self) { self = .array(array) return } throw CordinateError.missingValue } enum CordinateError: Error { case missingValue } } struct Geometry : Decodable { let date : String? let type : String? let coordinates : [Cordinate]? enum CodingKeys: String, CodingKey { case date = "date" case type = "type" case coordinates = "coordinates" } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) date = try values.decodeIfPresent(String.self, forKey: .date) type = try values.decodeIfPresent(String.self, forKey: .type) coordinates = try values.decodeIfPresent([Cordinate].self, forKey: .coordinates) } }
您可以使用Matt Thompson的酷库AnyCodable中的 AnyCodable
类型。
例如:
import AnyCodable struct Person: Codable { var id: AnyCodable }