Search code examples
swift4codable

Convert received Int to Bool decoding JSON using Codable


I have structure like this:

struct JSONModelSettings {
    let patientID : String
    let therapistID : String
    var isEnabled : Bool

    enum CodingKeys: String, CodingKey {
        case settings // The top level "settings" key
    }

    // The keys inside of the "settings" object
    enum SettingsKeys: String, CodingKey {
        case patientID = "patient_id"
        case therapistID = "therapist_id"
        case isEnabled = "is_therapy_forced"
    }
}

extension JSONModelSettings: Decodable {
    init(from decoder: Decoder) throws {

        // Extract the top-level values ("settings")
        let values = try decoder.container(keyedBy: CodingKeys.self)

        // Extract the settings object as a nested container
        let user = try values.nestedContainer(keyedBy: SettingsKeys.self, forKey: .settings)

        // Extract each property from the nested container
        patientID = try user.decode(String.self, forKey: .patientID)
        therapistID = try user.decode(String.self, forKey: .therapistID)
        isEnabled = try user.decode(Bool.self, forKey: .isEnabled)
    }
}

and JSON in this format (structure used to pull keys from setting without extra wrapper):

{
  "settings": {
    "patient_id": "80864898",
    "therapist_id": "78920",
    "enabled": "1"
  }
}

Question is how can i convert "isEnabled" to Bool, (getting 1 or 0 from API) When im trying to parse response im getting error: "Expected to decode Bool but found a number instead."


Solution

  • My suggestion: don't fight the JSON. Get it into a Swift value as quickly and with little fuss as possible, then do your manipulation there.

    You can define a private internal structure to hold the decoded data, like this:

    struct JSONModelSettings {
        let patientID : String
        let therapistID : String
        var isEnabled : Bool
    }
    
    extension JSONModelSettings: Decodable {
        // This struct stays very close to the JSON model, to the point
        // of using snake_case for its properties. Since it's private,
        // outside code cannot access it (and no need to either)
        private struct JSONSettings: Decodable {
            var patient_id: String
            var therapist_id: String
            var enabled: String
        }
    
        private enum CodingKeys: String, CodingKey {
            case settings
        }
    
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            let settings  = try container.decode(JSONSettings.self, forKey: .settings)
            patientID     = settings.patient_id
            therapistID   = settings.therapist_id
            isEnabled     = settings.enabled == "1"
        }
    }
    

    Other JSON mapping frameworks, such as ObjectMapper allows you to attach a transform function to the encoding/decoding process. It looks like Codable has no equivalence for now.