How to use Any in Codable Type

后端 未结 11 724
猫巷女王i
猫巷女王i 2020-11-29 04:20

I\'m currently working with Codable types in my project and facing an issue.

struct Person: Codable
{
    var id: Any
}

11条回答
  •  -上瘾入骨i
    2020-11-29 05:23

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

    That said, if you want to stick to it for your own reasons, you should write your own encoding/decoding and adopt some kind of convention in the serialized JSON.

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

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

    Examples

    Encode object with numeric id:

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

    Encode object with string id:

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

    Decode to numeric id:

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

    Decode to string id:

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

    An alternative implementation would be encoding to Int or String and wrap the decoding attempts in a do...catch.

    In the encoding part:

        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 to the right type in multiple 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 */
        }
    }
    

    It's uglier in my opinion.

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

提交回复
热议问题