Supporting separate Encoding- and DecodingKeys in Codable synthesis

I've just run into a use case for an extension of Codable synthesis and wanted to know if anyone else has ever desired this issue. The basic problem is that it's not always the case that there's a bijection between properties that should be decoded and properties that should be encoded. For example, I might have a response schema for a user that looks something like:

{
    "id": "5",
    "created_date": "1/1/2020",
    "modified_date": "2/26/2020",
    "total_comments": 5000
}

With a corresponding Swift model:

struct User {
    var id: Int
    var createdDate: String
    var modifiedDate: String?
    var totalComments: Int?
}

Adding a synthesized Codable conformance will faithfully encode and decode all of these properties, but modifiedDate and totalComments can easily become stale! If we want to e.g., persist a User object across launches, we may not want to populate a decoded User with those fields. Instead it would be better if those fields displayed blank/placeholder values until we could fetch a fresh User.

Proposed Solution

Allow types to specify properties to be encoded/decoded separately. The desired conformance would then look something like:

struct User: Codable {
    struct EncodingKeys: String, CodingKey {
        case id
        case createdDate = "created_date"
    }

    struct DecodingKeys: String, CodingKey {
        case id
        case createdDate = "created_date"
        case modifiedDate = "modified_date"
        case totalComments = "total_comments"
    }

    var id: Int
    var createdDate: String
    var modifiedDate: String?
    var totalComments: Int?
}

This would also support encoding properties that you don't necessarily want decoded, though I haven't thought of any obvious use cases for that.

Would love to hear everyone's thoughts, or any suggestions about how else to handle asymmetry between encoded/decoded properties. Does the work of writing out two coding key enums (and the added complexity to Codable synthesis) rise to the point that the response should just be "conform manually"?

In general, anytime you are doing something more complicated with Codable, it makes sense to completely decouple the encoding and decoding models from the app model. So in this case you would have separate types for the incoming UserResponse, persisted UserData, etcetera.

5 Likes

Yeah, that makes sense too. It just seems like that introduces a lot of overhead when really, UserData doesn't exist in-app at all—it's discarded immediately after (de)serialization. Suddenly you've duplicated the properties across two separate types, so there's nothing keeping them in sync if someone adds a property to UserResponse without thinking to update UserData (though I suppose a similar issue applies to failing to update En/DecodingKeys).

This should be possible to accomplish with a PropertyWrapper.

I already have one for omitting coding completely: @OmitCodding and just added an issue to add @OmitEncoding and @OmitDecoding I think it will be simple to add, so hope to have it released in the next couple days.

4 Likes