How to use Any in Codable Type
Quantum Value
First of all you can define a type that can be decoded both from a String
and Int
value.Here it is.
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 }}
Person
Now you can define your struct like this
struct Person: Decodable { let id: QuantumValue}
That's it. Let's test it!
JSON 1: id
is 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
is Int
let data = """{"id": 123}""".data(using: String.Encoding.utf8)!if let person = try? JSONDecoder().decode(Person.self, from: data) { print(person)}
UPDATE 1 Comparing values
This new paragraph should answer the questions from the comments.
If you want to compare a quantum value to an Int
you must keep in mind that a quantum value could contain an Int
or a String
.
So the question is: what does it mean comparing a String
and an Int
?
If you are just looking for a way of converting a quantum value into an Int
then you can simply add this extension
extension QuantumValue { var intValue: Int? { switch self { case .int(let value): return value case .string(let value): return Int(value) } }}
Now you can write
let quantumValue: QuantumValue: ...quantumValue.intValue == 123
UPDATE 2
This part to answer the comment left by @Abrcd18.
You can add this computed property to the Person
struct.
var idAsString: String { switch id { case .string(let string): return string case .int(let int): return String(int) }}
And now to populate the label just write
label.text = person.idAsString
Hope it helps.
Codable needs to know the type to cast to.
Firstly I would try to address the issue of not knowing the type, see if you can fix that and make it simpler.
Otherwise the only way I can think of solving your issue currently is to use generics like below.
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")
I solved this issue defining a new Decodable Struct called AnyDecodable, so instead of Any I use AnyDecodable. It works perfectly also with nested types.
Try this in a playground:
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)
You could extend my struct to be AnyCodable if you are interested also in the Encoding part.
Edit: I actually did it.
Here is 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")) } } }}
You can test it With the previous json in this way in a playground:
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)