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 */")
Encode object with id string:
var p2 = Person(id: "root") print(String(data: try JSONEncoder().encode(p2), encoding: String.Encoding.utf8) ?? "/* ERROR */")
Decoding to a numeric id value:
print(try JSONDecoder().decode(Person.self, from: "{\"id\": \"2\"}".data(using: String.Encoding.utf8)!))
Decode the id string:
print(try JSONDecoder().decode(Person.self, from: "{\"id\": \"admin\"}".data(using: String.Encoding.utf8)!))
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 { } if !id_decoded { do { if let idnum = try container.decodeIfPresent(Int.self, forKey: .id) { id = idnum } } catch { } }
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.