如何在 Codable 类型中使用 Any
How to use Any in Codable Type
我目前在我的项目中使用 Codable
类型并遇到问题。
struct Person: Codable
{
var id: Any
}
上述代码中的 id
可以是 String
或 Int
。这就是 id
类型为 Any
的原因。
我知道 Any
不是 Codable
。
我需要知道的是如何让它发挥作用。
Codable 需要知道要转换成的类型。
首先我会尝试解决不知道类型的问题,看看你是否可以解决这个问题并使其更简单。
否则我目前能想到的解决您的问题的唯一方法是使用如下的泛型。
struct Person<T> {
var id: T
var name: String
}
let person1 = Person<Int>(id: 1, name: "John")
let person2 = Person<String>(id: "two", name: "Steve")
您可以将 Any
替换为接受 Int
或 String
的枚举:
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 以了解如何执行此操作的示例。
我通过定义一个名为 AnyDecodable 的新 Decodable 结构解决了这个问题,因此我使用 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"))
}
}
}
}
您可以在 playground 中以这种方式使用之前的 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)
首先,正如您在其他答案和评论中看到的那样,使用 Any
并不是好的设计。如果可以的话,再考虑一下。
也就是说,如果你出于自己的原因要坚持下去,你应该自己写encoding/decoding,并在连载中采用某种约定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 "<Person id:\(id)>" }
}
例子
用数字编码对象 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)!))
// <Person id:2>
解码为字符串 id
:
print(try JSONDecoder().decode(Person.self,
from: "{\"id\": \"admin\"}".data(using: String.Encoding.utf8)!))
// <Person id:admin>
另一种实现方式是编码为 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 */
}
}
我觉得更丑。
根据您对服务器序列化的控制,您可以使用它们中的任何一个,也可以编写适合实际序列化的其他内容。
量子值
首先,您可以定义一个可以从 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)
}
更新 1 比较值
This new paragraph should answer the questions from the comments.
如果您想将量子值与 Int
进行比较,您必须记住量子值可能包含 Int
或 String
。
所以问题是:比较 String
和 Int
是什么意思?
如果您只是在寻找一种将量子值转换为 Int
的方法,那么您只需添加此扩展程序
extension QuantumValue {
var intValue: Int? {
switch self {
case .int(let value): return value
case .string(let value): return Int(value)
}
}
}
现在你可以写了
let quantumValue: QuantumValue: ...
quantumValue.intValue == 123
更新 2
这部分回答@Abrcd18留下的评论。
您可以将此计算 属性 添加到 Person
结构。
var idAsString: String {
switch id {
case .string(let string): return string
case .int(let int): return String(int)
}
}
现在要填充标签只需写
label.text = person.idAsString
希望对您有所帮助。
如果您的问题是不确定 id 的类型,因为它可能是字符串或整数值,我可以向您推荐此博客 post:http://agostini.tech/2017/11/12/swift-4-codable-in-real-life-part-2/
基本上我定义了一个新的 Decodable 类型
public struct UncertainValue<T: Decodable, U: Decodable>: 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<Int, String>
}
您将能够使用 id.value
访问您的 ID
Luca Angeletti 的解决方案没有涵盖一个极端情况。
比如Cordinate的类型是Double或者[Double],Angeletti的方案会报错:"Expected to decode Double but found an array instead"
在这种情况下,您必须在坐标中使用嵌套枚举。
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)
}
}
您可以简单地使用 AnyCodable
Matt Thompson 的酷库 AnyCodable 中的类型 AnyCodable。
例如:
import AnyCodable
struct Person: Codable
{
var id: AnyCodable
}
将密钥设置为 Any,我喜欢以上所有答案。但是,当您不确定服务器人员将发送哪种数据类型时,您可以使用 Quantum class(如上所述),但 Quantum 类型使用或管理起来并不困难。所以这是我的解决方案,使您的可解码 class 密钥成为 Any 数据类型(或 "id" 对于 obj-c 爱好者)
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 {
}
这里您的 id
可以是任何 Codable
类型:
Swift 4.2
struct Person<T: Codable>: Codable {
var id: T
var name: String?
}
let p1 = Person(id: 1, name: "Bill")
let p2 = Person(id: "one", name: "John")
感谢 Luka Angeletti 的回答 () 我已将 enum 更改为 struct 以便我们可以更轻松地使用它
struct QuantumValue: Codable {
public var string: String?
public var integer: Int?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let int = try? container.decode(Int.self) {
self.integer = int
return
}
if let string = try? container.decode(String.self) {
self.string = string
return
}
throw QuantumError.missingValue
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(string)
try container.encode(integer)
}
enum QuantumError: Error {
case missingValue
}
func value() -> Any? {
if let s = string {
return s
}
if let i = integer {
return i
}
return nil
}
}
Swift 5
这是关于来自 Luca Angeletti 的 (恕我直言)的更新,因此执行您的请求:
enum PersonAny: Codable {
case int(Int), string(String) // Insert here the different type to encode/decode
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 AnyError.missingValue
}
enum AnyError:Error {
case missingValue
}
}
// Your declaration
struct Person: Codable
{
var id: PersonAny
}
我目前在我的项目中使用 Codable
类型并遇到问题。
struct Person: Codable
{
var id: Any
}
上述代码中的 id
可以是 String
或 Int
。这就是 id
类型为 Any
的原因。
我知道 Any
不是 Codable
。
我需要知道的是如何让它发挥作用。
Codable 需要知道要转换成的类型。
首先我会尝试解决不知道类型的问题,看看你是否可以解决这个问题并使其更简单。
否则我目前能想到的解决您的问题的唯一方法是使用如下的泛型。
struct Person<T> {
var id: T
var name: String
}
let person1 = Person<Int>(id: 1, name: "John")
let person2 = Person<String>(id: "two", name: "Steve")
您可以将 Any
替换为接受 Int
或 String
的枚举:
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 以了解如何执行此操作的示例。
我通过定义一个名为 AnyDecodable 的新 Decodable 结构解决了这个问题,因此我使用 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"))
}
}
}
}
您可以在 playground 中以这种方式使用之前的 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)
首先,正如您在其他答案和评论中看到的那样,使用 Any
并不是好的设计。如果可以的话,再考虑一下。
也就是说,如果你出于自己的原因要坚持下去,你应该自己写encoding/decoding,并在连载中采用某种约定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 "<Person id:\(id)>" }
}
例子
用数字编码对象 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)!))
// <Person id:2>
解码为字符串 id
:
print(try JSONDecoder().decode(Person.self,
from: "{\"id\": \"admin\"}".data(using: String.Encoding.utf8)!))
// <Person id:admin>
另一种实现方式是编码为 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 */
}
}
我觉得更丑。
根据您对服务器序列化的控制,您可以使用它们中的任何一个,也可以编写适合实际序列化的其他内容。
量子值
首先,您可以定义一个可以从 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)
}
更新 1 比较值
This new paragraph should answer the questions from the comments.
如果您想将量子值与 Int
进行比较,您必须记住量子值可能包含 Int
或 String
。
所以问题是:比较 String
和 Int
是什么意思?
如果您只是在寻找一种将量子值转换为 Int
的方法,那么您只需添加此扩展程序
extension QuantumValue {
var intValue: Int? {
switch self {
case .int(let value): return value
case .string(let value): return Int(value)
}
}
}
现在你可以写了
let quantumValue: QuantumValue: ...
quantumValue.intValue == 123
更新 2
这部分回答@Abrcd18留下的评论。
您可以将此计算 属性 添加到 Person
结构。
var idAsString: String {
switch id {
case .string(let string): return string
case .int(let int): return String(int)
}
}
现在要填充标签只需写
label.text = person.idAsString
希望对您有所帮助。
如果您的问题是不确定 id 的类型,因为它可能是字符串或整数值,我可以向您推荐此博客 post:http://agostini.tech/2017/11/12/swift-4-codable-in-real-life-part-2/
基本上我定义了一个新的 Decodable 类型
public struct UncertainValue<T: Decodable, U: Decodable>: 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<Int, String>
}
您将能够使用 id.value
访问您的 IDLuca Angeletti 的解决方案没有涵盖一个极端情况。
比如Cordinate的类型是Double或者[Double],Angeletti的方案会报错:"Expected to decode Double but found an array instead"
在这种情况下,您必须在坐标中使用嵌套枚举。
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)
}
}
您可以简单地使用 AnyCodable
Matt Thompson 的酷库 AnyCodable 中的类型 AnyCodable。
例如:
import AnyCodable
struct Person: Codable
{
var id: AnyCodable
}
将密钥设置为 Any,我喜欢以上所有答案。但是,当您不确定服务器人员将发送哪种数据类型时,您可以使用 Quantum class(如上所述),但 Quantum 类型使用或管理起来并不困难。所以这是我的解决方案,使您的可解码 class 密钥成为 Any 数据类型(或 "id" 对于 obj-c 爱好者)
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 {
}
这里您的 id
可以是任何 Codable
类型:
Swift 4.2
struct Person<T: Codable>: Codable {
var id: T
var name: String?
}
let p1 = Person(id: 1, name: "Bill")
let p2 = Person(id: "one", name: "John")
感谢 Luka Angeletti 的回答 (
struct QuantumValue: Codable {
public var string: String?
public var integer: Int?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let int = try? container.decode(Int.self) {
self.integer = int
return
}
if let string = try? container.decode(String.self) {
self.string = string
return
}
throw QuantumError.missingValue
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(string)
try container.encode(integer)
}
enum QuantumError: Error {
case missingValue
}
func value() -> Any? {
if let s = string {
return s
}
if let i = integer {
return i
}
return nil
}
}
Swift 5
这是关于来自 Luca Angeletti 的
enum PersonAny: Codable {
case int(Int), string(String) // Insert here the different type to encode/decode
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 AnyError.missingValue
}
enum AnyError:Error {
case missingValue
}
}
// Your declaration
struct Person: Codable
{
var id: PersonAny
}