How to use Any in Codable Type

I am currently working with Codable types in my project and am facing a problem.

 struct Person: Codable { var id: Any } 

id in the above code can be either String or Int . This is the reason id is of type Any .

I know that Any not Codable .

I need to know how I can make it work.

+16
ios swift codable decodable
source share
10 answers

Codable must know the type to execute.

First, I would try to solve the problem of not knowing the type, see if you can fix this and make it simpler.

Otherwise, the only way to solve your problem at the moment is to use generics, as shown 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") 
+21
source share

Quantum value

First of all, you can define a type that can be decoded both from a String value and from Int . Here.

 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 } } 

Man

Now you can define your structure as follows

 struct Person: Decodable { let id: QuantumValue } 

It. Let's check it out!

JSON 1: id is a 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] Comparison of values

This new paragraph should answer the questions from the comments.

If you want to compare a quantum value with Int you must keep in mind that the quantum value may contain Int or String .

So the question is: what does comparing String and Int mean?

If you are just looking for a way to convert a quantum value to Int 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 
+19
source share

I solved this problem by defining a new Decodable Struct called AnyDecodable, so instead of Any, I use AnyDecodable. It works well with nested types.

Try this on the 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 can expand my structure as AnyCodable if you are also interested in the coding part.

Edit: I really 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 check it out. With previous json this way on the 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) 
+10
source share

If your problem is that it does not know the type of identifier, since it can be a string or an integer value, I can offer you this blog entry: http://agostini.tech/2017/11/12/swift-4-codable -in-real-life-part-2 /

I basically defined a new type of 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)")) } } } 

From now on, your Person object will be

 struct Person: Decodable { var id: UncertainValue<Int, String> } 

you will be able to access your id using id.value

+4
source share

You can replace Any with an enumeration that takes an Int or String :

 enum Id: Codable { case numeric(value: Int) case named(name: String) } struct Person: Codable { var id: Id } 

Then the compiler will complain that Id does not match Decodable . Since Id has related meanings, you need to implement this yourself. Read https://littlebitesofcocoa.com/318-codable-enums for an example of how to do this.

+2
source share

To make a key like Anyone , I like all of the above answers. But when you are not sure which data type your server guy will send, you use the Quantum class (as described above), but the Quantum type is a little difficult to use or manage. So here is my decision to make your class decodable key as Any data type (or "id" for obj-c lovers)

  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 } } } } 

Usage:

 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 { } 
+2
source share

First of all, as you can read in other answers and comments, using Any for this is not a good design. If possible, give him a second thought.

However, if you want to stick with it for your own reasons, you have to write your own encoding / decoding and accept some kind of convention in serialized JSON.

The code below implements it, encoding id always as a string and decoding to Int or String depending on the value found.

 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)>" } } 

Examples

Encode an object with a numeric id :

 var p1 = Person(id: 1) print(String(data: try JSONEncoder().encode(p1), encoding: String.Encoding.utf8) ?? "/* ERROR */") // {"id":"1"} 

Encode object with id string:

 var p2 = Person(id: "root") print(String(data: try JSONEncoder().encode(p2), encoding: String.Encoding.utf8) ?? "/* ERROR */") // {"id":"root"} 

Decoding to a numeric id value:

 print(try JSONDecoder().decode(Person.self, from: "{\"id\": \"2\"}".data(using: String.Encoding.utf8)!)) // <Person id:2> 

Decode the id string:

 print(try JSONDecoder().decode(Person.self, from: "{\"id\": \"admin\"}".data(using: String.Encoding.utf8)!)) // <Person id:admin> 

An alternative implementation would be to encode Int or String and complete the decoding attempts in do...catch .

In terms of coding:

  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) } 

And then decode the desired type with a few attempts:

 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 */ } } 

This is ugly in my opinion.

Depending on the control that you have over serializing the server, you can use either of them or write something else adapted to the actual serialization.

+1
source share

There is a corner housing that is not covered by the Luca Angeletti solution.

For example, if the type is Cordinate Double or [Double], Angeletti's solution will throw an error: "It is expected that it will decode Double, but will instead find an array"

In this case, you should use a nested enumeration instead in 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) } } 
0
source share

You can simply use the AnyCodable type from Matt Thompson's cool AnyCodable library.

For example:

 import AnyCodable struct Person: Codable { var id: AnyCodable } 
0
source share

Here your id can be any type of 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") 
0
source share

All Articles