Codextended 0.3.0

Codextended 0.3.0

John Sundell 维护。



  • 作者:
  • John Sundell

Codextended

Swift Package Manager Mac + Linux Twitter: @johnsundell

欢迎使用 Codextended — 一套旨在通过提供类型推断和便捷功能使 Swift 的 Codable API 更易于使用的外部库。它不是一个包装器,也不是一个新的框架,而是通过非常轻量级的方式直接增强 Codable

Codable是非常棒的!

没有任何第三方序列化框架能比得上 Codable 的便捷性。因为它内置,所以它可以利用编译器在许多情况下自动生成所有需要的序列化代码,并且可以作为多个不同模块之间的通用桥梁使用,而无需引入任何共享依赖项。

然而,一旦需要某种形式的定制——例如,转换解码数据的某些部分或为某些键提供默认值——标准的 Codable API 就开始变得 是冗长。它也没有利用 Swift 的强大类型推断能力,这产生了大量不必要的样板代码。

这就是 Codextended 旨在解决的问题。

示例

以下是一些示例,展示了使用原始的 Codable 与添加到 Codextended 中的 API 之间的区别。目标是把所有常见序列化操作都转换成一行代码,而不是设置大量的样板代码。

🏢顶级 API

Codextended 对编码和解码值所使用的顶级 API 进行了一些微小调整,使其能够利用类型推断,并能够使用正在编码或解码的实际值的实例方法。

🍨使用原始的 Codable

// Encoding
let encoder = JSONEncoder()
let data = try encoder.encode(value)

// Decoding
let decoder = JSONDecoder()
let article = try decoder.decode(Article.self, from: data)

🦸‍♀️ 使用 Codextended

// Encoding
let data = try value.encoded()

// Decoding
let article = try data.decoded() as Article

// Decoding when the type can be inferred
try saveArticle(data.decoded())

🔑重写单个键的行为

在序列化数据的格式与将使用它的 Swift 类型格式完全匹配时,Codable 是非常出色的——一旦我们只需要进行微小的调整,事情就会迅速从非常方便变成非常冗长。

为了举例,让我们假设我们只想为单个属性提供一个默认值(而无需将其设置为可选的,这样会使得在其他代码库中处理它更加困难)。为了实现这一点,我们需要完全手动实现我们类型的解码——如下面的Article类型的tags属性所示。

🍨使用原始的 Codable

struct Article: Codable {
    enum CodingKeys: CodingKey {
        case title
        case body
        case footnotes
        case tags
    }

    var title: String
    var body: String
    var footnotes: String?
    var tags: [String]

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        title = try container.decode(String.self, forKey: .title)
        body = try container.decode(String.self, forKey: .body)
        footnotes = try container.decodeIfPresent(String.self, forKey: .footnotes)
        tags = (try? container.decode([String].self, forKey: .tags)) ?? []
    }
}

🦸‍♂️ 在Codextended

struct Article: Codable {
    var title: String
    var body: String
    var footnotes: String?
    var tags: [String]

    init(from decoder: Decoder) throws {
        title = try decoder.decode("title")
        body = try decoder.decode("body")
        footnotes = try decoder.decodeIfPresent("footnotes")
        tags = (try? decoder.decode("tags")) ?? []
    }
}

Codextended包括对基于CodingKey的值和字符串字面量的解码重载,以便我们可以为每种具体情况选择最合适/方便的方法。

📆使用日期格式化程序

Codable已经通过为JSONEncoderJSONDecoder分配一个DateFormatter来支持自定义日期格式。然而,要求每个调用点都了解每种类型使用的特定日期格式并不总是很理想——因此,在Codextended中,类型自身很容易选择它需要使用的日期格式。

当与第三方数据一起工作并且我们只想定制我们某些类型的日期格式,或者在编码值时希望生成更容易阅读的日期字符串时,这非常有用。

🍨使用原始的 Codable

struct Bookmark: Codable {
    enum CodingKeys: CodingKey {
        case url
        case date
    }

    struct DateCodingError: Error {}

    static let dateFormatter = makeDateFormatter()

    var url: URL
    var date: Date

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        url = try container.decode(URL.self, forKey: .url)

        let dateString = try container.decode(String.self, forKey: .date)

        guard let date = Bookmark.dateFormatter.date(from: dateString) else {
            throw DateCodingError()
        }

        self.date = date
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(url, forKey: .url)

        let dateString = Bookmark.dateFormatter.string(from: date)
        try container.encode(dateString, forKey: .date)
    }
}

🦹‍♀️ 在Codextended

struct Bookmark: Codable {
    static let dateFormatter = makeDateFormatter()

    var url: URL
    var date: Date

    init(from decoder: Decoder) throws {
        url = try decoder.decode("url")
        date = try decoder.decode("date", using: Bookmark.dateFormatter)
    }

    func encode(to encoder: Encoder) throws {
        try encoder.encode(url, for: "url")
        try encoder.encode(date, for: "date", using: Bookmark.dateFormatter)
    }
}

再次,我们本来可以选择使用上面提到的CodingKeys枚举来表示我们的键,而不是使用内联字符串。

混合和匹配

由于Codextended是100%通过扩展实现的,因此您可以在同一个项目中轻松地将它与“原始”Codable代码混合匹配。它也不改变Codable的伟大之处——那就是它通常根本不需要任何手动代码,并且可以作为框架之间的桥梁。

它所做的仅仅是当需要某种形式的自定义时,为Codable提供“援助之手”。

安装

由于Codextended是在单个文件中实现的,因此使用它的最简单方法是将它直接拖放到您的Xcode项目中。

但如果您希望使用依赖项管理器,您可以使用Swift Package Manager,通过将Codextended声明为您的Package.swift文件中的一个依赖项来实现。

.package(url: "https://github.com/JohnSundell/Codextended", from: "0.1.0")

有关更多信息,请参阅Swift Package Manager文档

您还可以使用CocoaPods,在您的Podfile中添加以下行:

pod "Codextended"

贡献 & 支持

Codextended完全在公开状态下开发,非常欢迎您的贡献。

在您开始在您的任何项目中使用 Codextended 之前,强烈建议您花几分钟时间熟悉其文档和内部实现(所有内容都存储在一个文件中!),这样您就可以准备好应对您可能遇到的任何问题或边界情况。

要了解更多关于实现 Codextended 使用到的原则,请参阅 Sundell 的《Swift中的类型推断驱动序列化》。

该项目不提供基于 GitHub Issues 的支持,而是鼓励用户积极参与其持续开发——通过修复他们遇到的任何错误,或在哪里发现不足之处改进文档。

如果您想进行更改,请发起一个 Pull Request——即使它只是您计划中的更改草案或重现问题的测试用例——我们可以从那里进一步讨论。

希望您会喜欢使用 Codextended😀