GitHub - SwiftyLab/MetaCodable: Supercharge Swift's Codable implementations with macros meta-programming. (original) (raw)

MetaCodable

API Docs Swift Package Manager Compatible CocoaPods Compatible Swift Platforms CI/CD CodeFactor codecov

Supercharge Swift's Codable implementations with macros.

Overview

MetaCodable framework exposes custom macros which can be used to generate dynamic Codable implementations. The core of the framework is Codable(commonStrategies:) 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:

See the limitations for this macro.

Requirements

Platform Minimum Swift Version Installation Status
iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+ 5.9 Swift Package Manager, CocoaPods Fully Tested
Linux 5.9 Swift Package Manager Fully Tested
Windows 5.9.1 Swift Package Manager Fully Tested

Installation

Swift Package Manager

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"),

CocoaPods

CocoaPods is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate MetaCodable into your Xcode project using CocoaPods, specify it in your Podfile:

Usage

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.

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 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, memberwise initializer can be generated that uses this default value for the field.

@Codable @MemberInit struct CodableData { @Default("some") let field: String }

The memberwise initializer generated will look like this:

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

Use or create custom helpers to provide custom decoding/encoding.

Library provides following helpers that address common custom decoding/encoding needs:

And more, see the full documentation for HelperCoders for more details.

You can even create your own by conforming to HelperCoder.

Represent data with variations in the form of external/internal/adjacent tagging or lack of any tagging, with single enum with each case as a variation or a protocol type (lack of tagging not supported) that varies with conformances across modules.

i.e. while Swift compiler only generates implementation assuming external tagged enums, only following data:

[ { "load": { "key": "MyKey" } }, { "store": { "key": "MyKey", "value": 42 } } ]

can be represented by following enum with current compiler implementation:

enum Command { case load(key: String) case store(key: String, value: Int) }

while MetaCodable allows data in both of the following format to be represented by above enum as well:

[ { "type": "load", "key": "MyKey" }, { "type": "store", "key": "MyKey", "value": 42 } ]

[ { "type": "load", "content": { "key": "MyKey" } }, { "type": "store", "content": { "key": "MyKey", "value": 42 } } ]

See the full documentation for MetaCodable and HelperCoders, for API details and advanced use cases. Also, see the limitations.

Contributing

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.

License

MetaCodable is released under the MIT license. See LICENSE for details.