Applying JSONDecoder's keyDecodingStrategy to (String) enum payloads

Apologies if this has been discussed before, a quick search of threads regarding keyDecodingStrategy didn't turn up anything that looked like it addressed this specific question.

Consider the following simple model:

struct User: Decodable {
    enum AccountType: String, Decodable {
        case basic, pro, proUltra
    }
    var accountType: AccountType
}

This successfully decodes the payload {"account_type": "pro"} but fails to decode {"account_type": "pro_ultra"} with the error "Cannot initialize AccountType from invalid String value pro_ultra". The solution is to specify the raw value of the proUltra case manually, so that the enum is defined as

enum AccountType: String, Decodable {
    case basic, pro, proUltra = "pro_ultra"
}

This case encoding must be specified for all cases of all enums where the case name consists of more than one word. However, when a JSONDecoder is decoding User it must already have the context to know that the account_type key in the payload must map to the accountType property. This is easily accomplished via a keyDecodingStrategy of convertFromSnakeCase, but that context is left unused when decoding String-type enums.

Are there configuration options I'm not aware of that would enable this sort of decoding for enum payloads without having to specify the raw values explicitly? If not, should this use-case be supported?

I would imagine it's exceedingly rare for an API to provide payloads which look like {"account_type": "proUltra"}, so IMO it would be sensible default behavior to convert enum payloads using the same strategy that keyDecodingStrategy uses—however, I would imagine this is off the table due to backwards compatibility concerns. Options I can think of off-hand would be to provide a new configuration property on JSONDecoder, something like stringEnumDecodingStrategy, which could either be its own type or just be of type KeyDecodingStrategy. Alternatively, String, Decodable enums could offer a static property (e.g. mimicKeyDecodingStrategy) that would indicate that the enum payload should be decoded using the same strategy by which keys are converted.

Interested to hear other's thoughts on this topic!

Interesting. As far as the name implies, it makes sense that a “key” decoding strategy would not apply to “value” decoding. Still, I am inclined to agree that the likelihood is low of values with preset enumerated options not following the same naming convention as the keys in a particular payload.

Perhaps a new option that applies to keys and values would be warranted without removing the option for keys (for backwards compatibility or even just granularity of control)

1 Like

Yeah, I agree that it's somewhat counterintuitive for a "key" strategy to affect how values are decoded. OTOH, specifying the raw values manually feels almost exactly like defining a custom CodingKeys enumeration.

I don't think it's completely nonsensical to think of string enumerations as a set of mutually exclusive keys with no value—all the information you have is "key is present/not present." Nor can I think of a super compelling use case for allowing different strategies for decoding keys and enum values, so maybe the setting could just be a new Bool flag on JSONDecoder, something like treatEnumStringsAsKeys, that would make it apparent that the keyDecodingStrategy will apply to enums as well as keys.

Default conversions would have a negative performance impact for everyone, in a system that's already one of the slowest JSON decoders around. There are a lot of solutions to this problem, of which setting the rawValue of the enum is the most common and straightforward. At this point it seems clear that adding more and more parameters and types to customize decoding within particular decoders is not a scalable solution. Instead, a holistic reconsideration of the APIs offered in support Codable must be done to truly solve these problems once and for all.

4 Likes

Case in point: Your API emanates from .NET, where the keys are either camelCase or PascalCase, but enum cases are habitually written in all caps.

1 Like