Introducing MetaCodable: A Collection of Macros helping with custom Codable implementation generation

Hey everyone!

I'm excited to introduce my latest project, MetaCodable, a powerful macro library that will help you with all your Codable needs. Using MetaCodable, you can get rid of repetitive boilerplate code that you often have to write. Some of the feature it provides:

Custom `CodingKey` value declaration per variable, instead of requiring you to write for all fields.

i.e. in the official docs, to define custom CodingKey for 2 fields of Landmark type you had to write:

struct Landmark: Codable {
    var name: String
    var foundingYear: Int
    var location: Coordinate
    var vantagePoints: [Coordinate]
    
    enum CodingKeys: String, CodingKey {
        case name = "title"
        case foundingYear = "founding_date"
        
        case location
        case vantagePoints
    }
}

But with MetaCodable all you have to write is this:

@Codable
struct Landmark {
    @CodablePath("title")
    var name: String
    @CodablePath("founding_date")
    var foundingYear: Int

    var location: Coordinate
    var vantagePoints: [Coordinate]
}
Create flattened model for nested `CodingKey` values.

i.e. in official docs to decode a JSON like this:

{
  "latitude": 0,
  "longitude": 0,
  "additionalInfo": {
      "elevation": 0
  }
}

You had to write all these boilerplate:

struct Coordinate {
    var latitude: Double
    var longitude: Double
    var elevation: Double

    enum CodingKeys: String, CodingKey {
        case latitude
        case longitude
        case additionalInfo
    }

    enum AdditionalInfoKeys: String, CodingKey {
        case elevation
    }
}

extension Coordinate: Decodable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        latitude = try values.decode(Double.self, forKey: .latitude)
        longitude = try values.decode(Double.self, forKey: .longitude)
    
        let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
        elevation = try additionalInfo.decode(Double.self, forKey: .elevation)
    }
}

extension Coordinate: Encodable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(latitude, forKey: .latitude)
        try container.encode(longitude, forKey: .longitude)
    
        var additionalInfo = container.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
        try additionalInfo.encode(elevation, forKey: .elevation)
    }
}

But with MetaCodable all you have to write is this:

@Codable
struct Coordinate {
    var latitude: Double
    var longitude: Double
    
    @CodablePath("additionalInfo", "elevation")
    var elevation: Double
}
Provide default value in case of decoding failures and member-wise initializer generated considers these default values.

Instead of throwing error in case of missing data or type mismatch, you can provide a default value that will be assigned in this case. The memberwise initializer generated also uses this default value for the field. The following definition with MetaCodable:

@Codable
struct CodableData {
    @CodablePath(default: "some")
    let field: String
}

will not throw any error when empty JSON({}) or JSON with type mismatch({ "field": 5 }) is provided. The default value will be assigned in such case. Also, the memberwise initializer generated will look like this:

init(field: String = "some") {
    self.field = field
}

Besides these features you can create custom types with your own decoding/encoding strategies and pass those to be used by macros. I hope you find MetaCodable useful. Let me know if you have any suggestions or additional feature request.

Happy coding!

https://swiftylab.github.io/MetaCodable/documentation/metacodable

11 Likes

This looks fantastic.

1 Like

Hey everyone!

I am modifying the macro names and syntax for better readability, please see the full discussion and provide your feedback: Suggest upcoming change to macro syntax · SwiftyLab/MetaCodable · Discussion #13 · GitHub

Hey everyone!

Just released v1.1.0. This version includes some bug fixes and following major features:

  • Support for generic struct types conformance generation.
  • A new HelperCoders module with common custom decoding/encoding helpers, i.e. decoding with cudtom date and data formats, basic data types represented in other types etc.

For future version I am planning to add enum support and need your opinions to shape the design. Please feel free to chime in this discussion about how you are using enums currently.

1 Like

Nice! Only Base64 coding? Not hex? Base64 is the default for Data, so a Hex variant is that would benefit the most :heart_eyes:

I haven't seen hex being used to transfer data over web due to it being less efficient than base64. However, I would love to see some discussions how many users actually use it, PRs are also welcome :slightly_smiling_face:

The crypto industry uses hex a lot, because it makes it possible to read the data directly, which often is written in hex. I can see that a secp256k1 (bitcoin curve) public key is valid, since on compressed form it should start with 0x03 or 0x02

1 Like

Hey everyone!

Just released v1.2.0 which is packed with features:

and some bug fixes.

With the increase of features I am looking for any feedback and help on improving documentation.