Supercharge Swift's Codable implementations with macros.
MetaCodable framework exposes custom macros which can be used to generate dynamic Codable implementations. The core of the framework is Codable() macro which generates the implementation aided by data provided with using other macros.
MetaCodable aims to supercharge your Codable implementations by providing these inbox features:
- Allows custom
CodingKeyvalue declaration per variable, instead of requiring you to write all theCodingKeyvalues withCodedAt(_:)passing single argument. - Allows to create flattened model for nested
CodingKeyvalues withCodedAt(_:)andCodedIn(_:). - Allows to create composition of multiple
Codabletypes withCodedAt(_:)passing no arguments. - Allows to provide default value in case of decoding failures with
Default(_:). - Generates member-wise initializer(s) considering the above default value syntax as well.
- Allows to create custom decoding/encoding strategies with
HelperCoderand using them withCodedBy(_:). i.e.LossySequenceCoderetc.
| Platform | Minimum Swift Version | Installation | Status |
|---|---|---|---|
| iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+ | 5.9 | Swift Package Manager | Fully Tested |
| Linux | 5.9 | Swift Package Manager | Fully Tested |
| Windows | 5.9 | Swift Package Manager | Fully Tested |
The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler.
Once you have your Swift package set up, adding MetaCodable as a dependency is as easy as adding it to the dependencies value of your Package.swift.
.package(url: "https://github.com/SwiftyLab/MetaCodable.git", from: "1.0.0"),Then you can add the MetaCodable module product as dependency to the targets of your choosing, by adding it to the dependencies value of your targets.
.product(name: "MetaCodable", package: "MetaCodable"),MetaCodable allows to get rid of boiler plate that was often needed in some typical Codable implementations with features like:
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 {
@CodedAt("title")
var name: String
@CodedAt("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
@CodedAt("additionalInfo", "elevation")
var elevation: Double
}You can even minimize further using CodedIn macro since the final CodingKey value is the same as field name:
@Codable
struct Coordinate {
var latitude: Double
var longitude: Double
@CodedIn("additionalInfo")
var elevation: Double
}Provide default value in case of decoding failures and member-wise initializer(s) 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 {
@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
}See the full documentation for API details and advanced use cases.
If you wish to contribute a change, suggest any improvements, please review our contribution guide, check for open issues, if it is already being worked upon or open a pull request.
MetaCodable is released under the MIT license. See LICENSE for details.