Thanks for the input! Some comments:
This might be possible to do in the long run, but we'd likely need to come up with new names for the properties/types; once ABI stability hits, we'll need to keep these conversions working (though deprecated), even if they don't necessarily do the right thing.
I don't think removing allKeys is the right direction to go in (nor can we even do it). allKeys is used for types which want to dynamically iterate over the contents of a container, key by key, in order to decode some values. Dictionary itself does this, for example, in its init(from:) though you already know that in that particular case, we're looking to opt Dictionary keys out of the conversion process because of the type of data they represent (user data as opposed to model data). Types other than Dictionary, however, won't get this special exemption.
allKeys is most often helpful when you don't know what's in the payload but want to decode it anyway. Usually, the approach taken here is what Dictionary above does — define your own struct CustomKey : CodingKey type (or similar) which can take on any String or Int value; create a container keyed on that custom key type, and iterate allKeys, decoding values as you go. This allows you to get at all the values in a container, even if you don't know what keys would be used.
Because the conversion to allKeys is String-to-CodingKey, in this dynamic case, even though you'll always get all of the keys in the container, they won't be converted in the expected direction (e.g. you'd always successfully get back CustomKey("myHttpLink")).
- On the one hand, this might be alright — chances are, you'd use the key to immediately decode a value, in which case, the conversion going from
CodingKey-to-String will handle things (going from CustomKey("myHttpLink") back to "my_http_link")
- On the other, if you want to pass on the
stringValue of any of these keys to something else, i.e. you're doing something approximately likely what Dictionary does, the key values will be wrong. This is exactly what you're currently fixing in PR-16238, but for all other types
It's also perfectly reasonable to want to use allKeys to automate the decoding of a container with a lot of known possible key values:
private enum KnownAPIKeys : String, CodingKey {
case someAPIKey1
case someAPIKey2
// ...
case someAPIKey40
}
...
init(from decoder: Decoder) throws {
var container = ...
// ...
let apiPayloadContainer = try container.nestedContainer(keyedBy: KnownAPIKeys.self, ...)
self.apiPayload = [String : ...]()
for key in apiPayloadContainer.allKeys {
if let value = apiPayloadContainer.decodeIfPresent(..., forKey: key) {
self.apiPayload[key.stringValue] = value
}
}
}
In other words, it's perfectly reasonable to use a container keyed on CodingKeys and allKeys to filter a container based on the data you actually care about, provided by another API.
In this case, any of the API keys may be missing, but preserving them if they're there is important. If we break allKeys, data here would just be silently missing for any key which falls into this edge case.
Now, how common is this use case? I would guess that this is relatively rare. It would be difficult to find out, but it's important to consider. Data loss is no joke, and there won't be anything (within the language at least) that would help warn about this.
Figuring out what the PR breaks is the hard part.
In this case, allKeys is definitely used internally, but I care about external usages as well.
I don't think we can refine the protocol right now in this way without it being a breaking change. However, as mentioned above, this is exactly the opposite of when allKeys is most often used — when you don't know what the keys are (and thus, they cannot be CaseIterable).
We could likely do this, yes. That's a separate issue that we should pitch, though.
A warning about what? If the CodingKeys type could be CaseIterable, but isn't? I don't think we have reason to force folks to have CaseIterable enums if they don't care about them being so.
In all of this, I want to say that I am not against option 1, on principle or otherwise — I think it's a simple, clean solution that can fix issues that folks already have, and it seems like the biggest surprise is that things don't already work this way.
I do however want to make sure that given that we'd be shuffling one prominent edge case for another less common but more subtle edge case, we're willing to accept the cost after understanding the tradeoff.