swiftswift4codable

Decodable, doesn't decode optional enum with invalid value


I have defined a enum like this:

enum ClubLevel: Int, Codable {
    case golden = 1, silver, bronze
}

in my struct I have an optional property of type ClubLevel and when I decode this property in init(from decoder: Decoder):

self.clubLevel = try container.decode(ClubLevel?.self, forKey: .clubLevel)

I face this error:

debugDescription: "Cannot initialize ClubLevel from invalid Int value 0", underlyingError: nil"

I'm wondering even this property is optional, decoder won't continue

any idea?


Solution

  • The line

    self.clubLevel = try container.decode(ClubLevel?.self, forKey: .clubLevel)
    

    doesn't try to decode ClubLevel, assigning nil if unsuccessful. What it does is:

    1. Try to decode nil (represented in JSON as null) for the clubLevel key. If unsuccessful,
    2. Try to decode a ClubLevel for the clubLevel key. If unsuccessful,
    3. Throw an error

    So if the value for the clubLevel key is neither nil nor a valid ClubLevel representation, you'll get an error thrown. You'll note that this also means you'll get an error thrown if the clubLevel key is missing entirely (rather than being present with a value of nil).

    Ignoring missing keys is done with decodeIfPresent:

    self.clubLevel = try container.decodeIfPresent(ClubLevel.self, forKey: .clubLevel)
    

    This will now:

    1. Return nil if the clubLevel key is missing from the container. If they key is present,
    2. Try to decode nil (represented in JSON as null) for the clubLevel key. If unsuccessful,
    3. Try to decode a ClubLevel for the clubLevel key. If unsuccessful,
    4. Throw an error

    This is the default behaviour for decoding optionals in a compiler-generated implementation of init(from:). It will still throw an error in your case as the value for the clubLevel key is not a valid ClubLevel.

    If you want to just try and decode a ClubLevel, assigning nil on the decoding failing for any reason (key missing, invalid value, etc.), then you want to use try?:

    self.clubLevel = try? container.decode(ClubLevel.self, forKey: .clubLevel)