How to use Any in Codable Type How to use Any in Codable Type swift swift

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)