Contents

star-lord-phb/swift-codable-macro

Provide Swift Macros for automatically generate customizable implementation for conforming `Codable` protocol.

Introduction

Swift Compiler already provides auto implementation when we conform a custom type to Codable. It is nice when you are in charge of defining the encoding / decoding format, but unfortunately, that is not always the case. When the format is defined by others, we might have to use wired property name or even implement encode(to:) and init(:from) ourselves.

Such problem usually occurs when using an REST API provided by some third parties. For example, we might expect a Person struct to be something like this:

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

However, the response body of the REST API might, for some reason, be something like this:

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

In this case, we have to implement the conformance to Codable ourselves.

To handle such situation in a "swifter" way, the swift-codable-macro is here to help. For the example above, we can how declare the Person Struct 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
}

Done! The Codable conformance and implementation of encode(to:) and init(from:) will be generated automatically! No other additional implementation code required!

This is not all the capability of this package. The following codes provide a brief view of what it can do.

@Codable
struct Person {
    
    @CodingField("data", "uid")
    @DecodeTransform(source: String.self, with: { UUID(uuidString: $0)! })
    @EncodeTransform(source: UUID.self, with: \.uuidString)
    var id: UUID
    
    @CodingField("data", "meta", "name")
    @CodingValidate(source: String.self, with: { !$0.isEmpty })
    @CodingValidate(source: String.self, with: { !$0.contains(where: { $0.isNumber }) })
    var name: String 
    
    @CodingField("data", "meta", "gender", onMissing: .male, onMismatch: .female)
    let gender: Gender
    
    @CodingField("data", "meta", "birth", default: Date.distancePast)
    @CodingTransform(
        .date.timeIntervalTransform(), 
        .double.multiRepresentationTransform(encodeTo: .string)
    )
    var birthday: Date
    
    @CodingField("data", "meta", "known_programming_languages")
    @SequenceCodingField(
        subPath: "name", 
        onMissing: .ignore, 
        onMismatch: .value("Swift"),
        decodeTransform: Set.init
    )
    var knownProgrammingLanguages: Set<String>
    
    @CodingIgnore
	var habit: String = "writing Swift Macro"
    
}
@SingleValueCodable
struct FilePath {
    @SingleValueCodableDelegate
    var storage: String
    var isDir: Bool = false 
}
@EnumCodable(option: .adjacentKeyed())
enum Example {
    @EnumCaseCoding(caseKey: "a_key", emptyPayloadOption: .emptyObject)
    case a
    @EnumCaseCoding(caseKey: "a_key", payload: .singleValue)
    case b(Int)
    @EnumCaseCoding(payload: .object(keys: "key1", "key2", "key3"))
    case c(Int, label: String, _: Int)
}

Provided Macros

For more detailed guidance, see documentation

Keyed Coding

| Macro | Description | | -------------------------------------- | ------------------------------------------------------------ | | Codable(inherit:) | Annotate a class or a struct for auto conforming to Codable | | CodingField(:default:) | Provide custom coding path and default value for a property | | CodingField(:onMissing:onMismatch:) | Provide custom coding path and default value in different error cases for a property | | CodingIgnore | Ignore a property | | DecodeTransform(source:with:) | Specify a transformation when decoding | | EncodeTransform(source:with:) | Specify a transformation when encoding | | CodingTransform(_:) | Specify Coding Transformations for both Encoding and Decoding | | CodingValidate(source:with:) | Validation performed when the value for the property is decoded | | SequenceCodingField | Provide settings for properties that are Sequence |

Single Value Coding

| Macro | Description | | ------------------------------ | ------------------------------------------------------------ | | SingleValueCodable(inherit:) | Annotate a class or a struct for auto conforming to Codable by encoding the instance into a single value | | SingleValueCodableDelegate | Mark a member property as the required "single value" for encoding and decoding |

Enum Coding

| Macro | Description | | -------------------------------------------- | ------------------------------------------------------------ | | EnumCodable(option:) | Annotate an enum for auto conforming to Codable | | EnumCaseCoding(caseKey:payload) | Provide settings of case key and payload for an enum case with associated values | | EnumCaseCoding(caseKey:emptyPayloadOption) | Provide settings of case key and emptyPayload representation for an enum case without associated values | | EnumCaseCoding(unkeyedRawValuePayload:) | Provide a rawValue as the payload for an enum case without associated values | | EnumCaseCoding(unkeyedPayload:) | Provide settings of payload for an enum case with associated values |

The design of the APIs for Enum Coding is inspired by the Serde framework from Rust

Installation

In Package.swift, add the following line into your dependencies:

.package(url: "https://github.com/Star-Lord-PHB/swift-codable-macro.git", from: "3.0.0")

Add CodableMacro as a dependency of your target:

.target(
    name: "Target", 
    dependencies: [
        .product(name: "CodableMacro", package: "swift-codable-macro"),
    ]
)

Add import CodableMacro in your source code.

Package Metadata

Repository: star-lord-phb/swift-codable-macro

Default branch: main

README: README.md