Enum Codable Bug

Consider the following enum and json data:

enum Options: String, Codable {

    case one
    case two

    enum CodingKeys: String, CodingKey {
        case one = "first"
        case two = "second"
    }
    
}

let jsonData = """
"first"
""".data(using: .utf8)!

My understanding of the CodingKeys enum is that the raw value of the cases serve at the names of the keys in the json dictionary.

However, when I try to decode the data I get the following error:

let decoded = try JSONDecoder().decode(Options.self, from: jsonData)

print(decoded)

// Playground execution terminated: An error was thrown and was not caught:
//  ▿ DecodingError
//  ▿ dataCorrupted : Context
//    - codingPath : 0 elements
//    - debugDescription : "Cannot initialize Options from invalid String value first"
//    - underlyingError : nil

This doesn't make any sense to me. Compare this with the following struct:

struct Size: Codable {
    let size: Int
    
    enum CodingKeys: String, CodingKey {
        case size = "the_size"
    }
    
}

let sizesJSONData = """
{ "the_size": 5 }
""".data(using: .utf8)!

let decoded = try JSONDecoder().decode(Size.self, from: sizesJSONData)
print(decoded)  // success

Just as I expected, the raw value of the coding key—"the_size"—is used to decode the data. So why are enums different?

This doesn't match what the documentation says:

If the keys used in your serialized data format don't match the property names from your data type, provide alternative keys by specifying String as the raw-value type for the CodingKeys enumeration. The string you use as a raw value for each enumeration case is the key name used during encoding and decoding. The association between the case name and its raw value lets you name your data structures according to the Swift API Design Guidelines rather than having to match the names, punctuation, and capitalization of the serialization format you're modeling.

That's generally true, but in this case there is no dictionary. The enum encodes/decodes itself as a single-value container — no coding keys are involved.

To get what you want, you have to customize the raw values of the cases directly. This definition will work as you expect:

enum Options: String, Codable {
    case one = "first"
    case two = "second"
}
1 Like

To add to what @ole says, what's happening here is not a bug, but is indeed subtly misleading:

  • Enums actually do not currently synthesize Codable conformance — see Automatic Codable conformance for enums with associated values that themselves conform to Codable for a bit of background, but at the moment, there's no decided-upon representation of what encoded enums should look like; as such, no implementation is offered
  • The reason you are getting a default implementation of Codable is because Options is RawRepresentable with a RawValue of String. RawRepresentable types whose RawValue is a Codable primitive type (Int, UInt, Double, String, etc.) get a default implementation based on that RawValue; you can see the one for String
  • As @ole says, these RawRepresentable implementations encode and decode in terms of the underlying rawValue, and do so in and out of a single-value encoding/decoding container. That's why your keys aren't used

So indeed, you can either assign the raw values of the enum cases to match the representation you want (if feasible), or keep the values the same but provide your own Codable implementation to override the default.

2 Likes

After reading all this, it makes much more sense now. Thanks to both of you for explaining this to me in such detail. I now understand that it is not a bug.

1 Like