jsonswiftcodabledecodableencodable

How to decode single unusual property among many Decodable Swift?


I have a struct conforming to Decodable. It has 50 String properties and only one Bool. That bool is coming from server like a string "false"/"true" or sometimes like as integer 0/1, so cannot be decoded from the box. How can I make it decoding but not write huge amount of manual decoding of all that 50 String properties? Maybe somehow override decodeIfPresent for Bool, but I could not make it working. How I can avoid creating init with decoding everything and all that stuff manually and only handle that one Bool? Without computed property if possible.

struct Response: Decodable {
    var s1: String
    var s2: String
    var s3: String
    //...........
    var s50: String
    var b1: Bool
    var b2: Bool
}

Here is json example:

{
    "s1":"string"
    "s2":"string"
    "s3":"string"
    //..........
    "s50":"string"
    "b1":"true"
    "b2":"0"
}

Tried this but does not work(((

extension KeyedDecodingContainer { //Doesn't work, no execution
    func decodeIfPresent(_ type: Bool.Type, forKey key: K) throws -> Bool {
        return try! self.decodeIfPresent(Bool.self, forKey: key)
    }
    func decodeIfPresent(_ type: Bool.Type, forKey key: K) throws -> Bool? {
        return try? self.decodeIfPresent(Bool.self, forKey: key)
    }
}

Solution

  • One way to solve this is to write a property wrapper that handles the custom decoding, like this:

    @propertyWrapper
    struct FunnyBool: Decodable {
        var wrappedValue: Bool
    
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            if let value = try? container.decode(Bool.self) {
                wrappedValue = value
            }
            else if
                let string = try? container.decode(String.self),
                let value = Bool(string)
            {
                wrappedValue = value
            }
            else if
                let int = try? container.decode(Int.self),
                let value = int == 0 ? false : int == 1 ? true : nil
            {
                wrappedValue = value
            }
            else {
                // Default...
                wrappedValue = false
            }
        }
    }
    

    Use it like this:

    struct Response: Decodable {
        var s1: String
        var s2: String
        @FunnyBool var b: Bool
    }
    
    let json = #"{ "s1": "hello", "s2": "world", "b": 1 }"#
    let response = try! JSONDecoder().decode(Response.self, from: json.data(using: .utf8)!)
    dump(response)
    

    Playground output:

    ▿ __lldb_expr_11.Response
      - s1: "hello"
      - s2: "world"
      ▿ _b: __lldb_expr_11.FunnyBool
        - wrappedValue: true