JSONFeed

JSONFeed 是一个轻量级的 JSON Feed 解析器,用 Swift 4 编写,使用了全新的 Codable 协议和 Foundation 的全新 JSONDecoderJSONEncoder 类。它支持从 JSON 解码和编码为 JSON。

JSON Feed 格式类似于 RSS 和 Atom,由 Brent SimmonsManton Reece 于 2017 年 5 月 17 日发布。

要求

用法

解码

您可以按照以下方式从原始数据实例化一个 JSONFeed 对象

let feed = try? JSONFeed.make(from: data)

对于那些懒惰的开发者,JSONFeed+Fetch.swift 扩展提供了一种快速轻量级的方式从网络上获取 JSON Feed,而无需编写任何网络代码(无需担心 URLSessionAlamofire)。

JSONFeed.fetch("https://daringfireball.net/feeds/json") { (feed: JSONFeed?, error: Error?) in

    guard error == nil else { return }

    let titles = feed!.items[0..<5].map { $0.title ?? "No title" }

    print(titles.joined(separator: "\n"))
}

上面的示例打印了 Daring Fireball 的五个最新帖子

Designing the Worst Volume Sliders Possible
John Markoff to Interview Scott Forstall Next Week
Brian Merchant Has Tony Fadell on Tape
Inductive Charging Is Not ‘Wireless’
Microsoft Surface Laptop Teardown

解码器严格执行以下规范要求

如果甚至一个要求未得到满足,那么解码器将抛出一个错误,因此整个 Feed 被认为是无效的。

请注意,JSON Feed 规范声明 item 附件 mime_type 为必需的字符串,但 JSONFeed 将此属性定义为 String?。 这是为了更紧密地匹配规范的精神

但是,在某些情况下,可能缺少必需的元素。 如果 attachment 缺少 mime_type,您可能可以很好地处理它。 毕竟,当您下载附加文件时,Web 浏览器将提供 MIME 类型。 (您可能已经可以从文件后缀中猜到它。)

编码

您还可以将原生 Swift JSONFeed 对象编码为 JSON。

要将 feed 编码为数据

let data = try? feed.encodeToData()

要生成一个字符串

let string = try feed.encodeToString()

设计目标

该项目的主要目标(除了最明显的目标,即成功解析 JSON Feeds)是通过在 Swift 4 中使用新的 Codable 协议,以及 Foundation 中新的 JSONDecoderJSONEncoder 类来消除样板 JSON 解析代码。 从历史上看,您会从服务器获取 Data,通过受人尊敬的 JSONSerialization 类将该 Data 反序列化为 [String: Any] 字典,然后通过手动从字典中的键值对设置每个属性来实例化您的原生 Swift 对象。 使用 CodableJSONDecoder,您可以一步轻松地从 Data 直接到 JSONFeed,而无需字典和所有样板。

当我开始这个项目时,第二个最重要的目标是使解析器灵活且宽松。 在开发过程中,我发现这个设计目标与第一个设计目标相矛盾。 换句话说,Codable 的开箱即用行为非常严格。

日期

JSON Feed 规范定义了两个日期参数:date_publisheddate_modified。 两者都指定为 RFC3339 格式的可选字符串。 什么是 RFC3339?

它是 ISO 8601 扩展格式的一个符合子集。 通过使大多数字段和标点符号成为强制性的来实现简单性。

太棒了! ISO 8601 的“符合子集”!

😃 这意味着我们可以使用 JSONDecoder 中内置的 iso8601 日期解码策略,对吗?

😩 错误!

Apple 的 ISO8601DateFormatter 中存在一个 bug:它无法解析包含秒的小数的日期字符串。 RFC3339 允许秒的小数,但将其定义为“很少使用的选项”,如下所示

为了互操作性,应尽可能强制或省略很少使用的选项。 下面定义的格式仅包含一个很少使用的选项:秒的小数。 预计只有需要严格排序的日期/时间戳或具有异常精度要求的应用程序才会使用它。

JSON Feed 规范未指定此选项是“强制还是省略”。 大多数实际的 JSON Feeds 不使用秒的小数,但有些使用(例如,Flying Meat 博客和 Maybe Pizza?)。

幸运的是,JSONDecoder 通过 DateDecodingStrategy 枚举为日期解码提供了多个选项

/// The strategy to use for decoding `Date` values.
public enum DateDecodingStrategy {

    /// Defer to `Date` for decoding. This is the default strategy.
    case deferredToDate

    /// Decode the `Date` as a UNIX timestamp from a JSON number.
    case secondsSince1970

    /// Decode the `Date` as UNIX millisecond timestamp from a JSON number.
    case millisecondsSince1970

    /// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
    case iso8601

    /// Decode the `Date` as a string parsed by the given formatter.
    case formatted(DateFormatter)

    /// Decode the `Date` as a custom value decoded by the given closure.
    case custom((Decoder) throws -> Date)
}

如上所述,如果日期字符串包含秒的小数,则 iso8601 将失败。 另一个选项是 formatted(DateFormatter),但您会注意到 Apple 的 ISO8601DateFormatter 实际上是 Formatter 而不是 DateFormatter 的子类 - 这是因为 DateFormatter 并不特别适合子类化。 JSONFeed 使用最后一个选项:case custom((Decoder) throws -> Date)

Item 标识符

根据规范,feed 中的每个 item 都必须具有一个 id,该 id 唯一地标识该 item。 指定的数据类型是一个字符串,但是该规范允许“数字或其他类型”

id(必需,字符串)对于该 feed 的该 item 在一段时间内是唯一的。 如果 item 曾经更新过,则 id 应该保持不变。 新 item 绝不应使用先前使用过的 id。 如果 id 以数字或其他类型表示,则 JSON Feed 阅读器必须将其强制转换为字符串。 理想情况下,id 是 item 描述的资源的完整 URL,因为 URL 可以作为出色的唯一标识符。

如果我们简单地将 id 定义为 String 并使用 Decodable 的默认编译器生成的实现,那么当解析使用数字而不是字符串作为 id 参数的 feed 时,JSONDecoder 将抛出一个错误。 为了代替 Decodable 的自定义实现(这将引入大量样板),JSONFeed 采用了一种名为 AmbiguouslyTypedIdentifier 的自定义类型,它具有自己的 Codable 自定义实现,允许 idStringIntDouble。 这是在提供灵活性和最大限度地减少样板之间的一个很好的权衡。

URL vs String

JSON Feed 规范包含多个作为 URL 的参数。 在 JSONFeed 中,每个 URL 参数都声明为 String 而不是 Foundation URL。 为什么? 如果属性是 URL,并且 feed 包含无效的 URL,那么 JSONDecoder 将抛出一个错误,从而使整个 feed 无效。 使用 String 代替 URL 允许 JSONFeed 更加通用和宽松。 此外,JSON Feed 规范暗示了未在公共网络上发布的私有 Feeds 的可能性。 对于此用例,feed 作者可能会决定对 URL 参数使用相对路径而不是绝对路径。

用例

为什么有人需要用 Swift 编写的 JSON Feed 编码器/解码器?

集成

手动

只需将 JSONFeed.swift 和(可选)JSONFeed+Fetch.swift 拖到您的 Xcode 项目中即可。

Swift Package Manager

我正在努力添加对 Swift Package Manager 的支持。