Supercharge Swift's Codable implementations with macros meta-programming.
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:
CodingKey
value declaration per variable, instead of requiring you to write all the CodingKey
values with CodedAt(_:)
passing single argument.CodingKey
values with CodedAt(_:)
and CodedIn(_:)
.Codable
types with CodedAt(_:)
passing no arguments.CodingKey
s provided with CodedAs(_:_:)
.Default(_:)
.HelperCoder
and using them with CodedBy(_:)
. i.e. LossySequenceCoder
etc.CodedAs(_:_:)
and case value/protocol type identifier type different from String
with CodedAs()
.CodedAt(_:)
and case content path with ContentAt(_:_:)
.IgnoreCoding()
, IgnoreDecoding()
and IgnoreEncoding()
.CodingKeys(_:)
.IgnoreCodingInitialized()
unless explicitly asked to decode/encode by attaching any coding attributes, i.e. CodedIn(_:)
, CodedAt(_:)
,
CodedBy(_:)
, Default(_:)
etc.HelperCoder
s with MetaProtocolCodable
build tool plugin from DynamicCodable
types.See the limitations for this macro.
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 |
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 target
s of your choosing, by adding it to the dependencies
value of your target
s.
.product(name: "MetaCodable", package: "MetaCodable"),
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
:
pod 'MetaCodable'
MetaCodable
allows to get rid of boiler plate that was often needed in some typical Codable
implementations with features like:
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]
}
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
}
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
}
Library provides following helpers that address common custom decoding/encoding needs:
LossySequenceCoder
to decode only valid data while ignoring invalid data in a sequence, instead of traditional way of failing decoding entirely.ValueCoder
to decode Bool
, Int
, Double
, String
etc. basic types even if they are represented in some other type, i.e decoding Int
from "1"
, decoding boolean from "yes"
etc.Since1970DateCoder
) or date formatters (DateCoder
, ISO8601DateCoder
).Base64Coder
to decode/encode data in base64 string representation.And more, see the full documentation for HelperCoders
for more details.
You can even create your own by conforming to HelperCoder
.
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.
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.