Some thoughts here:
For the record, Codable is not meant to be a replacement for NSCoding. Both exist in the same space in a meaningfully complementary way: their goals are different, as are their approaches for reaching those goals.
Our intention is to continue offering both APIs so you can benefit from both approaches where relevant.
There is nothing preventing any of these from being done today (and all encoders already can encode and decode collections of heterogeneous elements), but what prevents you from encoding [String : Any] is the generic requirement on encode<T : Codable>(_ value: T, ...): if you made [String : Any] Codable (though I don't necessarily recommend this), you could just encode your dictionary.
What's not reasonable to do outside of the scope of your specific application is make the claim that all [String : Any] : Codable, because clearly, it's possible to construct a dictionary with non-Codable elements. You can do this today and it will just work, but the standard library will never offer this.
Preventing the storage and retrieval and of heterogeneous collections has nothing at all to do with security: preventing it does not make an archive more secure, and allowing it does not make an archive less secure. Nor is this NSSecureCoding's goal. The goal of NSSecureCoding is rather limited in scope given the backwards compatibility requirements given NSCoding: preventing arbitrary code execution from happening inside of apps based on trust of malicious archives.
Again, my talk from this year's WWDC covers this in more detail, but the goal there is to prevent arbitrary trust of class names already in the archive: if I ask to decode an NSArray containing NSStrings, I shouldn't get back an NSMachPort. The dynamic design of NSCoding within the context of Objective-C makes it easier to be able to ask for an NSArray containing both NSStrings and NSNumbers, but this is no more or less secure.
In fact, the implication here is reversed. Due to the design decisions regarding putting class names in archives leading us to put type information elsewhere (i.e. in code), we are able to avoid trusting the contents of the archive (since there is nothing to trust). The additional security is a benefit that we get, not a reason to make it more difficult to express heterogeneity.
I meant to mention this above — although it's currently not possible to do, I suspect that Swift already embeds enough metadata in applications that this should be possible for at least public types. One leading question here is: should you be able to look up internal, private, and fileprivate types by name? And if so, from where? Would we start enforcing visibility at runtime? Not clear.
This has been considered, but there are a few obvious limitations:
-
What, if anything, prevents two types from claiming the same archiving name? This is not possible to prevent at compile time (unlike Objective-C which makes this easier: using the class name ensures uniqueness; if you've got two classes with the same name at runtime, all bets are already off anyway)
-
Up-front registration does not solve all problems: what if your [String : Any] contains an unnamed type vended by a different framework whose type and archiving name you know nothing about? How do you know to request to register their identifier, and how do you ask the type to do so?
FWIW, this exact registration problem is non-trivial to solve, and we're currently dealing with the challenge in some new API design internally. Swift currently does not offer an easy solution like Objective-C's +load, which would allow arbtirary frameworks to register their types in a global table at load time, but there are ways around this. Not all generalize to what you're looking for here.
All in all, the design decisions made for Codable came from years of experience with the NSCoding and NSSecureCoding APIs, their flaws, and their benefits. These APIs will continue to coexist, and if you truly need polymorphism or heterogeneity in a way that is impossible for Swift to represent (which I don't believe is the case), you always have those APIs to fall back on.
But, from that experience, I maintain than rather than going with named types, it is significantly easier to express (and benefit from the type safety of)
enum StringOrInt : Codable {
case string(String)
case int(Int)
init(from decoder: Decoder) throws { ... }
func encode(to encoder: Encoder) throws { ... }
}
let myCollection: [String : StringOrInt] = /* ... */
let data = try JSONEncoder().encode(myCollection)
over the NSSecureCoding analogue.
I've mentioned this in other threads, but what I'd really love to eventually see is variadic generics so we could offer
struct OneOf<T... : Codable> : Codable {
case t0(T[0])
case t1(T[1])
// ...
}
and everyone could benefit from [String : OneOf<String, Int>] rather than having to write their own type.