I'm trying to implement a protocol with similar functionality to how Codable
uses the CodingKeys
enum.
With Codable
and CodingKeys
, if you don't implement a case in the CodingKeys
enum for every property on the Codable
object, it causes a compiler error stating that the object does not conform to the protocol.
I have looked through documentation, and the only thing I can find related to the Codable
(Encodable
and Decodable
) protocol is the requirement to implement the func encode(to encoder: Encoder)
and init(from decoder: Decoder)
functions.
The closest I've gotten is defining a protocol as follows:
protocol TestProtocol {
associatedType Keys: CodingKey
}
This requires the implementer to have a Keys
property that conforms to CodingKey
, but it does not enforce the requirement to have a case for all properties. Additionally, you cannot declare the Keys
property as private like you can with Codable
.
Are Codable
and CodingKeys
handled at a deeper level than what is exposed through the APIs?
If not, is there a way to implement the CodingKeys
functionality outside of Codable
?
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 allEncodable
get an automatically generatedString
-backedCodingKey
enum
mapping properties to case names. Similarly forDecodable
types whose properties are allDecodable
- Types falling into (1) — and types which manually provide a
CodingKey
enum
(namedCodingKeys
, directly, or via atypealias
) whose cases map 1-to-1 toEncodable
/Decodable
properties by name — get automatic synthesis ofinit(from:)
andencode(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:)
andencode(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.