user2161301
user2161301

Reputation: 834

Swift JSONDecoder replace all missing keys by default value?

I want to set up a remote configuration file for staged feature releases, seasonal changes, offers etc. that wouldn't be worth going through App Review for.

I used to use a \n separated text file but for multi-line strings this gets a bit awkward real quick.

Instead of importing some bloated framework like Firebase for this, I am currently writing a small singleton that parses a configuration file from a remote URL.

I am however facing one issue now:

If the remote json contains a key that's not defined in my Codable struct everything works fine, I still get my object with all the defined keys. Not so much the other way around, if the json is missing a key defined in the struct, JSONDecoder is not able to decode. Example:

    let testJSON = """
{"version":1,"includedB":"B","___notIncludedC":"C"}
"""

struct DefaultConfiguration : Codable {
    var version = 1
    var includedB = "2"
    var notIncludedC = "3"
}

I can make the decoding """work""" by defining the notIncludedC to be of an optional String? - this however makes the result be nil after decoding instead of keeping its predefined default value.

All the answers on SO mention defining custom methods for each key, but I would rather like to use a "skip unknown and keep the value" approach because for big JSON's that would come with a lot of overhead code.

Upvotes: 6

Views: 1644

Answers (1)

Christos Koninis
Christos Koninis

Reputation: 1688

As a it was described in the comments you will have to write your own init() since the synthesized one can not provide the behavior you need:

let testJSON = """
{"version":1,"includedB":"B","notIncludedC":"C"}
"""

struct DefaultConfiguration : Codable {
    var version = 1
    var includedB = "2"
    var notIncludedC = "3"

    enum CodingKeys: String, CodingKey {
        case version
        case includedB
        case notIncludedC
     }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        version = try container.decode(Int.self, forKey: .version)
        includedB = try container.decode(String.self, forKey: .includedB)
        notIncludedC = try container.decodeIfPresent(String.self, forKey: .notIncludedC) ?? "3"
    }
}

Upvotes: 7

Related Questions