Add support for Encoding and Decoding nested JSON keys

Thanks for looking into this, Cal. I have a few thoughts.

First, as mentioned above, I don't think that it is correct that parsing the example struct requires a custom implementation. This would also work:

struct EvolutionProposal: Codable {
    var id: String
    var title: String
    var metadata: Metadata
    struct Metadata: Codable {
        var reviewStartDate: Date
        var reviewEndDate: Date
    }
}

In this case, you wouldn't even need custom keys. The problem is of course that perhaps you don't want to expose this detail as API. I don't think it's actually that difficult to fix that:

struct EvolutionProposal: Codable {
    public var id: String
    public var title: String
    public var reviewStartDate: Date { metadata.reviewStartDate }
    public var reviewEndDate: Date { metadata.reviewEndDate }

    private var metadata: Metadata
    private struct Metadata: Codable {
        var reviewStartDate: Date
        var reviewEndDate: Date
    }
}

So, secondly, this brings us to one of the core ideas of Codable -- representation of your encoded value using Swift types as much as possible. Moving part of the structure into the key name is (sometimes) going in the opposite direction. Using "regular" private/public and struct types is, I think, more intuitive of a solution than requiring a strategy to be set in a different type.

Now, as the author of the key strategy implementation, I'm fully aware that sometimes the strategy approach can be useful. However, there are two main drawbacks:

  1. It applies "globally" across the entire archive. That moves part of the behavior of how encode/decode works from the type itself (where the most knowledge about structure lies) into the encoder/decoder.
  2. It does not apply across different kinds of encoders and decoders. If EvolutionProposal specified the keys with the . syntax then it would effectively require JSONEncoder to encode and decode itself, because part of the data structure is now part of the key name instead.

It's not a black-and-white API boundary. The strategy enums exist along this continuum of control belonging to the Coder vs the type itself.

With that in mind, can we continue to find improvements to reduce the boilerplate of the public/private split above? I think the answer is yes, and especially yes if we consider language improvements along with it that may be enablers. One idea would be a kind of anonymous type for struct Metadata, which is (other than Codable) not very useful. I'm not enough of a language expert to make a concrete proposal here, but perhaps others have an idea.

Another may be adding API to KeyedDecodingContainer (et. al.) that allows decoding with a key path like "x.y.z". Presumably we would need to figure out how to implement it generically, but if we could do that then all decoders would get an implementation of it for free and it would decouple the type's exact format from JSON.

18 Likes