JSEN

GitHub Action Build Status Swift 5.4 Supports iOS, macOS, tvOS, watchOS and Ubuntu Latest release

/ˈdʒeɪsən/ JAY-sən

JSEN(JSON Swift 枚举表示法)是一个轻量级的 JSON 枚举表示,使用 Swift 编写。

JSON,正如 ECMA-404 标准 中定义的那样,可以是

因此,JSON 可以表示为递归枚举(或者 Swift 中的 indirect enum),从而有效地在 Swift 中创建静态类型的 JSON 负载。

我为什么要使用它?

这是无处不在的代码库中用于表示 JSON 的臭名昭著的 [String:Any] 的类型安全版本。如果你热爱 Swift,这应该已经足够成为一个理由了 😉 如果还不够,请继续阅读以发现这个简单枚举中存在的所有语法糖!

安装

使用 Swift Package Manager

dependencies: [
    .package(name: "JSEN", url: "https://github.com/rogerluan/JSEN", .upToNextMajor(from: "1.0.0")),
]

用法

我认为为了理解它的简单性,可视化 JSEN 声明至关重要

/// A simple JSON value representation using enum cases.
public enum JSEN : Equatable {
    /// An integer value.
    case int(Int)
    /// A floating point value.
    case double(Double)
    /// A string value.
    case string(String)
    /// A boolean value.
    case bool(Bool)
    /// An array value in which all elements are also JSEN values.
    indirect case array([JSEN])
    /// An object value, also known as dictionary, hash, and map.
    /// All values of this object are also JSEN values.
    indirect case dictionary([String:JSEN])
    /// A null value.
    case null
}

就是这样。

ExpressibleBy…Literal

现在你已经熟悉了 JSEN,它提供了一些语法糖实用程序,例如符合大多数 ExpressibleBy…Literal 协议

当你想要构建像这样的 JSON 结构时,符合 ExpressibleBy…Literal 协议非常棒

let request: [String:JSEN] = [
    "key": "value",
    "another_key": 42,
]

但是如果你不使用字面量呢?

let request: [String:JSEN] = [
    "amount": normalizedAmount // This won't compile
]

引入…

% 前缀运算符

let request: [String:JSEN] = [
    "amount": %normalizedAmount // This works!
]

自定义的 % 前缀运算符将任何 IntDoubleStringBool[JSEN][String:JSEN] 值转换为其各自的 JSEN 值。

根据设计,没有添加将 Optional 转换为 .null 的支持,以防止误用。

点击此处展开说明为什么这可能导致错误的原因

为了说明围绕 %optionalValue 操作可能出现的问题,请想象以下场景

let request: [String:JSEN] = [
    "middle_name": %optionalString
]
network.post(path: "user", parameters: request)
network.put(path: "user", parameters: request)
network.patch(path: "user", parameters: request)
network.mergePatch(path: "user", parameters: request)

在上述场景中,你认为 RESTful 预期的行为应该是什么?

如果 % 运算符检测到非空的 String,那很好。但是,如果它检测到其底层值为 .none(即 nil),它会将该值转换为 .null,当编码时,它将被转换为 NSNull()(更多信息请参见下面的 Codable 部分)。正如你所想象的那样,在 RESTful API 方面,NSNull()nil 的行为非常不同——前者可能会删除数据库上的键信息,而后者将被 Swift Dictionary 简单地忽略(就好像该字段根本不存在一样)。

因此,如果你想使用可选值,请显式调用,要么使用 .null 如果你知道该值必须编码为 NSNull() 实例,要么解包其值并将其包装在非空的 JSEN case 之一中。

符合 Codable

当然!我们不能错过这个。JSEN 原生支持 Encodable & Decodable(即 Codable),因此你可以轻松地将 JSEN 解析为/从 JSON 类似结构。每种 case 都映射到其各自的值类型,而 .null 映射到 NSNull() 实例(在 JSON 中,它由 null 表示)。

还添加了一个额外的实用程序,即 decode(as:) 函数。它接收一个符合 Decodable 协议的 Type 作为参数,并将尝试使用两步策略将 JSEN 值解码为给定的类型

使用 KeyPath 下标

最后但并非最不重要的是,是 KeyPath 下标。

基于 @olebegemann文章KeyPath 是一个简单的结构体,用于表示字符串的多个段。它可以通过字符串字面量(例如 "this.is.a.keypath")初始化,并且在初始化时,字符串会按句点分隔,从而构成结构体的段。

JSEN 的下标允许以下语法

let request: [String:JSEN] = [
    "1st": [
        "2nd": [
            "3rd": "Hello!"
        ]
    ]
]
print(request[keyPath: "1st.2nd.3rd"]) // "Hello!"

没有这种语法,你将不得不创建多个笨拙的可选链,并以奇怪且冗长的方式解包它们,才能访问字典中的嵌套值。我不是那样做的粉丝 :)

贡献

如果你发现任何错误、遗漏,或者你想对本项目提出改进建议,请提交 Issue 或 Pull Request 并附上你的想法,我保证在 24 小时内回复你!😇

参考

JSEN 主要基于 Swift 中的静态类型 JSON 负载 以及 Stack Overflow 和 Swift 论坛中传播的此实用程序的其他各种实现。我将我需要的一切都集中在这个项目中,因为我找不到类似的 Swift Package,它拥有我需要的一切。

许可证

本项目是开源的,并受标准的 2 条款 BSD 许可证保护。这意味着你可以使用(公开、商业和私有)、修改和分发本项目的内容,只要你提及 Roger Oba 作为此代码的原始作者,并在你的应用程序、存储库、项目或研究论文中复制 LICENSE 文本。

探索我的其他工具

Statused Social Banner

忘记 “v2.1.3 版本是什么时候上线的?” 和 “应用准备好测试了吗?”

Statused 监控 App Store Connect 并在 Slack 上直接向你发送通知。

了解更多:statused.com

联系方式

Twitter: @rogerluan_