I added a few more sections to Alternatives Considered:
Name the type CodingPath
instead of CodingKeyPath
In the pitch thread for this proposal, it was brought up that the name CodingKeyPath
could potentially cause confusion with the existing KeyPath
type. We could potentially choose a different name for this type, like CodingPath
.
We would also need to rename the other types and methods added in this proposal:
-
encoder.keyPathContainer(keyedBy: CodingKeyPaths.self)
would becomeencoder.pathContainer(keyedBy: CodingPaths.self)
-
KeyPathEncodingContainer
would becomePathEncodingContainer
Enable this behavior by setting a static flag on the CodingKeys
type
We could potentially allow authors to opt-in to this behavior by configuring a static flag on their CodingKeys
type:
// In the Standard Library:
public protocol CodingKey {
// A new protocol requirement:
static var options: CodingKeyOptions { get }
}
public struct CodingKeyOptions {
var dotNotationRepresentsNestedPath: Bool
}
// Default configuration to preserve source compatability and existing behavior:
public extension CodingKey {
static var options: CodingKeyOptions {
CodingKeyOptions(dotNotationRepresentsNestedPath: false)
}
}
// EvolutionProposal.swift
struct EvolutionProposal: Codable {
enum CodingKeys: String, CodingKey {
case id
case title
case reviewStartDate = "metadata.review_start_date"
case reviewEndDate = "metadata.review_end_date"
static var options: CodingKeyOptions {
CodingKeyOptions(dotNotationRepresentsNestedPath: true)
}
}
}
This approach seems appealing on the surface:
- We would only need to introduce one new type to the Standard Library (
CodingKeyOptions
) -
CodingKeyOptions
could be extended in the future to provide other customization points.- For example, we could add a key-transformation option similar to
Foundation.JSONEncoder.KeyEncodingStrategy.convertToSnakeCase
.
- For example, we could add a key-transformation option similar to
The unfortunate downside is that it's not possible to introduce new behavior on the existing CodingKeys
type without breaking backward compatability with existing Encoder
and Decoder
implementations.
- We could update Foundation's encoders and decoders (
JSONEncoder
,PlistEncoder
, etc.) to respect these new options, but existing third-party implementations would also need to be updated. - We shouldn't introduce options that aren't guaranteed to be respected in the concrete
Encoder
orDecoder
implementation being used.
The only way to add new behavior to all existing Encoder
and Decoder
implementations is to introduce a new enhanced version of CodingKey
, along with corresponding enchanced KeyedEncodingContainer
and KeyedDecodingContainer
wrappers:
/// Like a `CodingKey`, but with additional configuration options. ("CodingKey 2.0")
public protocol ConfigurableCodingKey {
var stringValue: String { get }
var intValue: Int? { get }
static var options: CodingKeyOptions { get }
}
public struct CodingKeyOptions {
var dotNotationRepresentsNestedPath: Bool
}
public extension Encoder {
func container<ConfigurableKey: ConfigurableCodingKey>(keyedBy: ConfigurableKey) -> ConfiguredKeyedEncodingContainer<ConfigurableKey>
}
/// This `ConfigurableKeyedEncodingContainer` would wrap existing `KeyedEncodingContainer` implementations,
/// which would allows the Standard Library to apply additional transformations.
/// All existing `Encoder` implementations would get this support "for free".
public struct ConfigurableKeyedEncodingContainer<ConfigurableKey: ConfigurableCodingKey> {
private let underlyingKeyedEncodingContainer: KeyedEncodingContainer<_>
public func encode<T: Encodable>(_ value: T, atKey key: ConfigurableKey) {
// Apply transformations to the key as specified by the `CodingKeyOptions`
// The Standard Library can add arbitrary complex key transformations here
// and it would apply to all existing `Encoder` implementations.
}
}
// along with a corresponding `ConfigurableKeyedDecodingContainer` implementation.
-
The
CodingKeyPath
implementation in this proposal uses this exact approach to add additional behavior on top of the existingKeyedEncodingContainer
andKeyedDecodingContainer
APIs. -
This would be an improvement over the existing
CodingKeys
type, but it has worse ergonomics thanCodingKeys
and the proposedCodingKeyPaths
.-
The author belives there aren't enough additional use cases for a
static
CodingKeyOptions
customization point for it to pull its syntactic weight. -
Static type-level configuration is less useful than per-property configuration, which cannot be done ergonomically using the existing
CodingKeys
design.
-
-
A "key" and a "path" have fundamentally different encoding and decoding semantics. It seems more appropriate to treat a
CodingKeyPath
as a distinct type rather than a flag or option on someCodingKey
type.
Introduce an annotation-based alternative to CodingKeys
Instead of building upon the design of CodingKeys
, we could design an entirely new system using property-wrapper-like annotations.
struct EvolutionProposal: Codable {
// @Key("id") (compiler-synthesized)
var id: String
// @Key("title") (compiler-synthesized)
var title: String
@Path("metadata.review_start_date")
var reviewStartDate: Date
@Path("metadata.review_end_date")
var reviewEndDate: Date
}
The author believes it's more appropriate to extend and built upon the existing CodingKeys
-based system:
-
CodingKeys
cannot be removed or replaced, since that would be massively source-breaking. - The language should not include two separate / competing
Codable
systems.