Custom Coding Path for Codable with Swift Macro

The auto-generated implementation for Codable conformance in swift is good enough in many cases. However, if the one who defines the coding format is not us, things might get complicated. For example, we might have a Person Structure as follow:

struct Person: Codable {
    var id: String 
    var name: String
    var age: Int
}

However, the response body from some REST API may, for some reason, looks like this:

{
    "data": {
        "id": "9F6E1D7A-EF6A-4F7A-A1E0-46105DD31F3E",
        "meta": {
            "name": "Serika",
            "age": 15
        }
    }
}

In this case, we basically have to implement encode(to:) and init(from:) ourselves, which can be quite annoying especially when trying to come up with a name for the CodingKey enum.

Proposed Solution

For such cases, some macros for automatically generating the implementation can be useful. For the example above, we can design something like this:

@Codable
public struct Person {
    @CodingField("data", "id")
    var id: String
    @CodingField("data", "meta", "name")
    var name: String 
    @CodingField("data", "meta", "age")
    var age: Int
}

Based on the provided coding path, the macro can automatically generate the implementation for Codable conformance.

This can be further extended to support Optional type, default value or even "Ignored Property", for example:

@Codable
public struct Person {

    @CodingField("data", "id")
    var id: String?  
     // Will be `nil` if the key is missing when decoding

    @CodingField("data", "meta", "name")
    var name: String = "" 
    // Specify default value using a standard initializer 
    // Will be "" if the key is missing when decoding

    @CodingField("data", "meta", "age", default: 18)
    let age: Int
    // Specify the default value using the `default` parameter
    // Suitable when the property is a let constant since 
    // decoding won't work if a standard initializer is provided
    // Will be 18 if the key is missing when decoding 

    @CodingIgnore
    var privateNotes: String = ""
    // Will be completely ignored for both encoding and decoding 
}

I have tried to provide an implementation of this solution in this repo

With the flexibility of Macro and Swift Diagnostic, I have added some simple validation steps when generating the codes. For example, check whether the macro is attached to a stored property, check whether two properties have conflicted paths, and check whether a "ignored" property is optional or has a default value. Informative compiler error message will be provided when any of these problem are found.

Future Direction

The current design is mainly for solving the "coding path", "default value" and "ignored property" problems, but some more advanced encoding features such as customizing the coding format of String and Date are possible and can be considered in future versions.

2 Likes

I think this proposal is one step ahead of itself. In my opinion we (as in the community) should propose macro-ized Encodable and Decodable first and the list those niceties like ignoring properties or your paths in Future directions and do them in a follow up proposal.

2 Likes