swiftcodabledecodableencodable

Codable and CodingKeys


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?


Solution

  • 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:

    1. 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
    2. 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
    3. 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.