You ask two questions. I think it will be easier to explain them in reverse order.
Are Codable and CodingKeys are handled at a deeper level than what is exposed through the APIs?
Yes, the Swift compiler knows about the Encodable
, Decodable
, and CodingKey
protocols and has special code for them.
The compiler can synthesize a CodingKey
-compliant enum
named CodingKeys
, the init(from:)
initializer, and the encode(to:)
method, if some conditions are met. The conditions are spelled out in SE-0166:
Encodable
& Decodable
requirements can be automatically synthesized for certain types as well:
- Types conforming to
Encodable
whose properties are all Encodable
get an automatically generated String
-backed CodingKey
enum
mapping properties to case names. Similarly for Decodable
types whose properties are all Decodable
- Types falling into (1) — and types which manually provide a
CodingKey
enum
(named CodingKeys
, directly, or via a typealias
) whose cases map 1-to-1 to Encodable
/Decodable
properties by name — get automatic synthesis of init(from:)
and encode(to:)
as appropriate, using those properties and keys
- Types which fall into neither (1) nor (2) will have to provide a custom key type if needed and provide their own
init(from:)
and encode(to:)
, as appropriate
Note that the CodingKey
-compliant type does not in general have to be named CodingKeys
or even be an enum
unless you are relying on compiler-synthesized conformance.
Furthermore, note that a CodingKeys
type conforming to CodingKey
only needs to have a case for every member of its enclosing type if you are relying on the compiler to synthesize init(from:)
or encode(to:)
.
If you're manually implementing init(from:)
and encode(to:)
, you can use any name for your CodingKey
-compliant type, and it only has to have the cases you care about. You don't even need a CodingKey
-compliant type if you're only using a single-value container or an unkeyed container for storage.
If not, is there a way to implement the CodingKeys functionality outside of Codable?
If, by “functionality”, you mean the way the compiler automatically synthesizes implementations, then the only way is by using a code generator (like Sourcery or gyb) to generate source code and feed it to the compiler.
If, by “functionality”, you mean the way the compiler requires a key member for each Encodable
/Decodable
member of the enclosing type, then the only way is by running a separate program that analyzes your source code and errors out if any case is missing. You can't make the standard Swift compiler do it for you.