With JSONDecoder in Swift 4, can missing keys use a default value instead of having to be optional properties?

Swift 4 added the new Codable protocol. When I use JSONDecoder it seems to require all the non-optional properties of my Codable class to have keys in the JSON or it throws an error.

Making every property of my class optional seems like an unnecessary hassle since what I really want is to use the value in the json or a default value. (I don't want the property to be nil.)

Is there a way to do this?

class MyCodable: Codable {
    var name: String = "Default Appleseed"
}

func load(input: String) {
    do {
        if let data = input.data(using: .utf8) {
            let result = try JSONDecoder().decode(MyCodable.self, from: data)
            print("name: \(result.name)")
        }
    } catch  {
        print("error: \(error)")
        // `Error message: "Key not found when expecting non-optional type
        // String for coding key \"name\""`
    }
}

let goodInput = "{\"name\": \"Jonny Appleseed\" }"
let badInput = "{}"
load(input: goodInput) // works, `name` is Jonny Applessed
load(input: badInput) // breaks, `name` required since property is non-optional

You can implement the init(from decoder: Decoder) method in your type instead of using the default implementation:

class MyCodable: Codable {
    var name: String = "Default Appleseed"

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let name = try container.decodeIfPresent(String.self, forKey: .name) {
            self.name = name
        }
    }
}

You can also make name a constant property (if you want to):

class MyCodable: Codable {
    let name: String

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let name = try container.decodeIfPresent(String.self, forKey: .name) {
            self.name = name
        } else {
            self.name = "Default Appleseed"
        }
    }
}

or

required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Default Appleseed"
}

Re your comment: With a custom extension

extension KeyedDecodingContainer {
    func decodeWrapper<T>(key: K, defaultValue: T) throws -> T
        where T : Decodable {
        return try decodeIfPresent(T.self, forKey: key) ?? defaultValue
    }
}

you could implement the init method as

required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.name = try container.decodeWrapper(key: .name, defaultValue: "Default Appleseed")
}

but that is not much shorter than

    self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Default Appleseed"

You can use a computed property that defaults to the desired value if the JSON key is not found.

class MyCodable: Codable {
    var name: String { return _name ?? "Default Appleseed" }
    var age: Int?

    // this is the property that gets actually decoded/encoded
    private var _name: String?

    enum CodingKeys: String, CodingKey {
        case _name = "name"
        case age
    }
}

If you want to have the property readwrite, you can also implement the setter:

var name: String {
    get { _name ?? "Default Appleseed" }
    set { _name = newValue }
}

This adds a little extra verbosity as you'll need to declare another property, and will require adding the CodingKeys enum (if not already there). The advantage is that you don't need to write custom decoding/encoding code, which can become tedious at some point.

Note that this solution only works if the value for the JSON key either holds a string, or is not present. If the JSON might have the value under another form (e.g. its an int), then you can try this solution.


Approach that I prefer is using so called DTOs - data transfer object. It is a struct, that conforms to Codable and represents the desired object.

struct MyClassDTO: Codable {
    let items: [String]?
    let otherVar: Int?
}

Then you simply init the object that you want to use in the app with that DTO.

 class MyClass {
    let items: [String]
    var otherVar = 3
    init(_ dto: MyClassDTO) {
        items = dto.items ?? [String]()
        otherVar = dto.otherVar ?? 3
    }

    var dto: MyClassDTO {
        return MyClassDTO(items: items, otherVar: otherVar)
    }
}

This approach is also good since you can rename and change final object however you wish to. It is clear and requires less code than manual decoding. Moreover, with this approach you can separate networking layer from other app.


You can implement.

struct Source : Codable {

    let id : String?
    let name : String?

    enum CodingKeys: String, CodingKey {
        case id = "id"
        case name = "name"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        id = try values.decodeIfPresent(String.self, forKey: .id) ?? ""
        name = try values.decodeIfPresent(String.self, forKey: .name)
    }
}