There are two answers to your questions here:
There are corner cases here around integer coding keys which don't always have good answers. The biggest issue to deal with is conversion between integer sizes:
- It's always safe to promote an integer type smaller than
Int
to anInt
, so it would be possible to encode those values asCodingKey
s. On decode, though, you could get aCodingKey
whose integer value does not fit in, say, anInt8
. You could consider this to be a type mismatch, potentially, but it's one thing for an integer value to not map to a realCodingKey
, and another for the integer value to be completely out of bounds. Given the API contract ofCodingKey
s, it's reasonable to assume that they should be able to use the entire range ofInt
values; it's a different matter to expect to decode anInt8
value from an unkeyed container and get back something that would require anInt64
or similar - A bigger problem is what happens for types like
UInt
— if a dictionary hasUInt
keys which all happen to fit in anInt
, should it be encoded as a keyed container? And if one of the keys does not fit in anInt
? There is an inconsistency in formats here based on the actual values of the keys, and not the types themselves
It is considerably simpler to define the behavior here around the CodingKey
s contract itself: all keys have a String
value, and potentially an Int
value. Those types map losslessly into CodingKey
s; otherwise, we do the safe thing of not making assumptions.
The casts would work, certainly, but the question is one of semantics. Just because a type conforms to LosslessStringConvertible
, does it mean that it's safe/reasonable to convert it into a string key and back?
- There are types for which the string representation might be significantly larger or more difficult to represent than the underlying
Codable
representation - Considering also the interaction with the above types:
Int8
isLosslessStringConvertible
, which means that in a format that differentiates betweenInt
andString
keys, encoding[1: "one", 2: "two"]
as[Int: String]
would encode as[1: "one", 2: "two"]
, while encoding the same value as[Int8: String]
would encode it as["1": "one", "2": "two"]
; the differences here might be non-obvious if you don't know the original types
From a semantic standpoint, though — it's not necessarily clear what the intent is behind annotating a type with LosslessStringConvertible
; just because a type is indeed convertible doesn't necessarily mean we should prioritize that format over their own Codable
format.
None of these are insurmountable differences, by the way; it's possible to come up with semantics that make sense for all of these cases. It is, however, significantly simpler to express that "String
- and Int
-keyed dictionaries use CodingKey
s; everything else uses the unkeyed format".