JSONFeed
是一个轻量级的 JSON Feed 解析器,用 Swift 4 编写,使用了全新的 Codable
协议和 Foundation 的全新 JSONDecoder
和 JSONEncoder
类。它支持从 JSON 解码和编码为 JSON。
JSON Feed 格式类似于 RSS 和 Atom,由 Brent Simmons 和 Manton Reece 于 2017 年 5 月 17 日发布。
您可以按照以下方式从原始数据实例化一个 JSONFeed
对象
let feed = try? JSONFeed.make(from: data)
对于那些懒惰的开发者,JSONFeed+Fetch.swift
扩展提供了一种快速轻量级的方式从网络上获取 JSON Feed,而无需编写任何网络代码(无需担心 URLSession
或 Alamofire
)。
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
解码器严格执行以下规范要求
version
字符串。title
字符串。items
数组,尽管该数组可能为空。id
,并且该 id
必须是字符串、整数或浮点数。date_published
和 date_modified
)必须符合 RFC3339。url
字符串。type
字符串和一个 url
字符串。title
的值应该是一个字符串——如果解码器遇到 title
的整数、浮点数或其他类型,它将抛出一个错误。如果甚至一个要求未得到满足,那么解码器将抛出一个错误,因此整个 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 中新的 JSONDecoder
和 JSONEncoder
类来消除样板 JSON 解析代码。 从历史上看,您会从服务器获取 Data
,通过受人尊敬的 JSONSerialization
类将该 Data
反序列化为 [String: Any]
字典,然后通过手动从字典中的键值对设置每个属性来实例化您的原生 Swift 对象。 使用 Codable
和 JSONDecoder
,您可以一步轻松地从 Data
直接到 JSONFeed
,而无需字典和所有样板。
当我开始这个项目时,第二个最重要的目标是使解析器灵活且宽松。 在开发过程中,我发现这个设计目标与第一个设计目标相矛盾。 换句话说,Codable
的开箱即用行为非常严格。
JSON Feed 规范定义了两个日期参数:date_published
和 date_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)
。
根据规范,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
自定义实现,允许 id
为 String
、Int
或 Double
。 这是在提供灵活性和最大限度地减少样板之间的一个很好的权衡。
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 的支持。