Indeed, as @jrose notes, this is neither an oversight, nor a bug, but the intended behavior. See, for instance, SR-9023 for discussion about encoding e.g. [UUID : String]. As an example, let's use UUID.
UUID can choose to encode as anything that it wants (and chooses to encode as a String in the common case), but Dictionary has no knowledge of that, especially not prior to actually encoding the value. In order to encode its contents, Dictionary must choose a container, and at that, it has to choose between something keyed and something unkeyed. Keyed containers can only accept CodingKeys as keys, and CodingKeys can only be formed from Strings and Ints; you cannot form a CodingKey from a UUID without stringifying the UUID first, and Dictionary has no knowledge of UUID specifically to be able to do that.
As such, the only thing that Dictionary can fall back to doing is encoding key-value pairs into an unkeyed container.
This is true for all types which are not Int or String as far as Dictionary is concerned — if it can't go into a CodingKey, it can't be used as a native dictionary key.
Note that we have SR-7788 tracking the implicit conversion of things which are RawRepresentable as String or Int [this is currently not being done], and you can imagine something similar for things which are LosslessStringConvertible. However, we would have to be very careful about backwards compatibility here, as something as innocuous as SR-1858 UUID should conform to RawRepresentable would suddenly break backwards compatibility for everyone.
So, as usual, in cases of ambiguity like this, the right solution is to indicate exactly what you want by writing the code: if you expect the UUID to be encoded as a String, you will have to perform that conversion.
One thing we can do to at least make this easier, for instance, is offer an adaptor type which takes a strategy for converting from arbitrary types to CodingKeys for use as dictionary keys, and you can use that rather than having to write it yourself.
Note also that Encoders and Decoders may choose to intercept specific types and encode them in a different format (e.g. JSONEncoder/JSONDecoder encode URLs as strings rather than dictionaries); Dictionary can't make any static assumptions about arbitrary types, since the specific Encoder/Decoder might have a different representation in mind.