PMJSON
PMJSON 提供了一个纯 Swift 强类型 JSON 编码/解码器,以及一系列便捷方法来实现到/从 Foundation 对象的转换以及解码 JSON 结构。
整个 JSON 编码/解码器可以在没有 Foundation 的情况下使用,只需从项目中删除 ObjectiveC.swift
和 DecimalNumber.swift
文件。项目余下的部分只依赖于 Darwin
,使用 strtod()
和 strtoll()
。ObjectiveC.swift
文件提供了在 JSON
值和 Foundation 对象之间进行转换以及从 Data
中解码的便捷方法,而 DecimalNumber.swift
文件提供了将值转换为 NSDecimalNumber
的便捷访问器。
用法
在深入了解之前,这里有一个为结构体编写解码器的简单示例。处理不完整数据的方法有很多(例如,是否忽略错误类型的值,以及是否尝试将非字符串值强制转换为字符串或反之亦然),但以下示例将比较严格,对类型错误的值将抛出错误。
struct Address {
var streetLine1: String
var streetLine2: String?
var city: String
var state: String?
var postalCode: String
var country: String?
init(json: JSON) throws {
streetLine1 = try json.getString("street_line1")
streetLine2 = try json.getStringOrNil("street_line2")
city = try json.getString("city")
state = try json.getStringOrNil("state")
postalCode = try json.toString("postal_code") // coerce numbers to strings
country = try json.getStringOrNil("country")
}
}
以下是解码嵌套数组值的示例
struct Person {
var firstName: String
var lastName: String? // some people don't have last names
var age: Int
var addresses: [Address]
init(json: JSON) throws {
firstName = try json.getString("firstName")
lastName = try json.getStringOrNil("lastName")
age = try json.getInt("age")
addresses = try json.mapArray("addresses", Address.init(json:))
}
}
如果您不想处理错误并且只想处理可选值,也可以这样做
struct Config {
var name: String?
var doThatThing: Bool
var maxRetries: Int
init(json: JSON) {
name = json["name"]?.string
doThatThing = json["doThatThing"]?.bool ?? false
maxRetries = json["maxRetries"]?.int ?? 10
}
}
此库还提供对 Swift.Encoder
和 Swift.Decoder
的支持。有关详细信息,请参阅 本节。
解析
JSON解码器分为单独的解析和解码阶段。解析器消费任何Unicode标量序列,并产生一个类似SAX XML解析器的JSON "事件"。解码器接受一个JSON事件序列,并生成一个JSON
值。这种架构设计使得你可以只使用解析器来直接解码到自己的数据结构中,如果需要的话,甚至可以完全绕过JSON
表示。然而,大多数客户端预期会使用这两个组件,这是通过简单的方法JSON.decode(_:options:)
公开的。
将JSON字符串解析为JSON
值就像这样
let json = try JSON.decode(jsonString)
JSON解析器中的任何错误都表示为JSONParserError
值,并由decode()
方法抛出。该错误包含错误的确切行和列,以及描述问题的代码。
还提供了一个方便的方法,用于从包含UTF-8、UTF-16或UTF-32编码数据的Data
中解码
let json = try JSON.decode(data)
将JSON
值编码也很简单
let jsonString = JSON.encodeAsString(json)
你还可以直接编码到任何TextOutputStream
JSON.encode(json, toStream: &output)
同样,还提供了一个用于处理Data
的方便方法
let data = JSON.encodeAsData(json)
JSON流
PMJSON支持解析JSON流,多个顶层JSON值可以有可选空白分隔符(例如{"a": 1}{"a": 2}
)。使用此功能的最简单方法是使用JSON.decodeStream(_:)
,它返回一个包含JSON
值或JSONParserError
错误的惰性序列的JSONStreamValue
。您还可以直接使用《JSONParser》和《JSONDecoder》来获得对流的更精细控制。
JSONParser
和JSONDecoder
如上所述,JSON解码器分为解析和解码两个阶段。JSONParser
是解析阶段,它封装任何Unicode标量序列,自身是JSONEvent
序列。一个JSONEvent
是一个JSON解析的步骤,如遇到{
时的.objectStart
,或者在遇到"string"
时的.stringValue(_)
。如果您想进行任何类型的懒处理(例如,如果您在处理单个巨大的JSON数据块,并且不想一次性将整个内容解码到内存中),可以直接使用JSONParser
来发射事件流。
同样地,JSONDecoder
是解码阶段。它封装一系列 JSONEvent
,将这些事件解码成适当的 JSON
值。封装的序列也必须符合另一个独立的协议 JSONEventIterator
,该协议提供了行/列信息,用于错误输出。如果您想为除 JSONParser
之外的事件序列封装,或者想要一个不同于 JSONStreamDecoder
提供的 JSON 流解码接口,您可以直接使用 JSONDecoder
。
由于其拆分性质,您可以选择提供自己的事件流或解码阶段。或者,您可以对 JSONParser
进行适配,以修改在传递给解码器之前的事件(这样做可能比将生成的 JSON
值转换为其他类型更有效)。
获取器
除了编码/解码外,这个库还提供了一套获取 JSON
价值观的获取器。提供了4种基本类型:
- 基本属性获取器,命名方式例如
.string
。这些获取器在其值为适当类型时返回底层值,如果值不是正确的类型,则返回nil
。例如,.string
返回String?
。这些获取器不进行类型之间的转换,例如JSON.Int64(42).string
返回nil
。 - 以
as
开头的属性获取器,如.asString
。这些获取器也返回一个可选值,但如果合适,它们会进行类型之间的转换。例如,JSON.Int64(42).asString
返回"42"
。 - 以
get
开头的方法,如getString()
。这些方法返回非可选值,并在值的类型不匹配时抛出JSONError
。这些方法不进行类型之间的转换,例如尝试JSON.Int64(42).getString()
会抛出错误。对于此类方法,还有一个以OrNil
结尾的变体,例如getStringOrNil()
,它也会返回一个可选值。这些方法只在值是null
时返回nil
,否则抛出错误。 - 以
to
开头的方法,如toString()
。这些方法与get
方法类似,但在适当的情况下将类型进行转换,使用与as
方法相同的规则,例如尝试JSON.Int64(42).toString()
返回"42"
。像get
方法一样,也有以OrNil
结尾的变体。
JSON
还提供了键和索引的获取器,它们总是安全的调用(即使没有边界索引也是如此)。
- 对于每个基本
get
获取器,都有一个变体接受一个键或索引。这些类似于对接收者进行索引访问然后再调用获取器,除了它们会产生更好的错误(并且妥善处理缺少的键/超出范围的索引)。例如,getString("键")
或getString(index)
。具有OrNil
变体的获取器如果键不存在或索引超出范围,则返回nil
。 - 类似地,还有用于
to
获取器的索引等效。
最后,getObject()
和 getArray()
获取器提供接受闭包的变体。建议使用这些变体代替基本获取器,因为它们会产生更好的错误。例如,给定以下 JSON
{
"object": {
"elements": [
{
"name": null
}
]
}
}
以及以下代码
try json.getObject("object").getArray("elements").getObject(0).getString("name")
此代码引发的错误将描述为"名称:期望字符串,找到null"
。
但有以下等效代码:
try json.getObject("object", { try $0.getArray("elements", { try $0.getObject(0, { try $0.getString("name") }) }) })
此代码引发的错误将描述为"object.elements[0].name:期望字符串,找到null"
。
所有这些访问器都在JSONObject
类型上可用(它是表示对象的类型)。
上面的最后一小段代码看起来非常冗长,但在实践中,你不会写出这样的代码。相反,你通常会只写下类似以下内容
try json.mapArray("elements", Element.init(json:))
Helps
JSON
类型有静态方法map()
、flatMap()
和compactMap()
,用于处理数组(因为PMJSON没有定义自己的数组类型)。使用这些方法而不是使用等效的SequenceType
方法的优点是PMJSON静态方法能产生更好的错误。
还有转换为/从Foundation对象的帮助器。JSON
提供了一个初始化器init(ns: Any) throws
,它可以将任何兼容JSON的对象转换为JSON
。JSON
和JSONObject
都提供了一个属性.ns
,它返回与JSON
相当的Foundation对象,以及.nsNoNull
,它执行同样的任务,但省略任何null
值而不是使用NSNull
。
支持Codable
JSON
类型符合Codable
协议,因此它可以编码为Swift.Encoder
并从Swift.Decoder
解码。这已经在标准库提供的JSONEncoder
和JSONDecoder
方面进行了测试。由于解码协议的限制,解码JSON
必须尝试解析多种不同类型的值,因此如果Swift.Decoder
编写得不好,解码JSON
可能会产生意外结果。
将数据编码到JSON.Encoder
并从JSON.Decoder
解码已优化以避免不必要的操作。
Swift.Encoder
和Swift.Decoder
此库提供了一个名为JSON.Encoder
的Swift.Encoder
实现。它可以编码任何Encodable
为JSON
、String
或Data
。其使用方法与Swift.JSONEncoder
类似(但截至目前,它没有选项来控制特定类型的编码)。
此库提供了一个名为JSON.Decoder
的Swift.Decoder
实现。它可以解码任何Decodable
为JSON
、String
或Data
。其使用方法与Swift.JSONDecoder
类似(但截至目前,它没有选项来控制特定类型的解码)。
性能
测试套件包含一些基本的性能测试。使用 PMJSON 解码大约 70KiB 的 JSON 大约需要 NSJSONSerialization
所需时间的 2.5-3 倍,但未使用不同输入分布进行测试,因此这种性能可能是特定于测试输入特征的。然而,将相同的 JSON 编码回 Data
实际上更高效,大约是 NSJSONSerialization
所需时间的 75%。这些基准测试是在 Swift 2.x 上进行的,因此自那时以来数值可能已发生变化。
要求
作为框架安装,至少需要 iOS 8,OS X 10.9,watchOS 2.0 或 tvOS 9.0。
安装
安装完成后,可以通过在代码中添加 import PMJSON
使用它。
Swift 包管理器
可以使用 Swift 包管理器 通过将其添加到您的 dependencies
列表来安装 PMJSON。
let package = Package(
name: "YourPackage",
dependencies: [
.package(url: "https://github.com/postmates/PMJSON.git", from: "3.0.1")
]
)
Carthage
要使用 Carthage 安装,请将以下内容添加到您的 Cartfile 中
github "postmates/PMJSON" ~> 3.0
此版本支持 Swift 4。如果您需要 Swift 3.x 支持,则可以使用
github "postmates/PMJSON" ~> 2.0
CocoaPods
使用 CocoaPods 进行安装,请在 Podfile 中添加以下内容
pod 'PMJSON', '~> 3.0'
此版本支持 Swift 4。如果您需要 Swift 3.x 支持,则可以使用
pod 'PMJSON', '~> 2.0'
许可证
此许可证归以下之一
- Apache License, Version 2.0 (LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT 许可证(《LICENSE-MIT 或 https://open-source.org.cn/licenses/MIT)根据您的选择。
贡献
除非您明确指出,否则您提交的任何旨在包含在工作中的贡献将按照上述双重许可,不附加任何附加条款或条件。
版本历史
v4.0.0 (2019-11-14)
- 更新到 Swift 5。
- 当使用
JSON.Encoder
和JSON.Decoder
对URL
进行编码/解码时,请对它们的绝对字符串进行编码和解码,而不是依赖于将它们作为对象进行编码的本地实现。这与JSONEncoder
和JSONDecoder
的行为相匹配。 - 修正了对
JSON.Encoder.DateEncodingStrategy.iso8601WithFractionalSeconds
的可用性属性。 - 将
JSON.Encoder.DateEncodingStrategy.iso8601WithFractionalSeconds
和JSON.Encoder.DateEncodingStrategy.iso8601WithFractionalSeconds
提升至 iOS 11.2+ 和 tvOS 11.2+,尽管该常量被标记为可用,但在运行时不支持。(#33) - 将
JSONObject.ns
和JSONObject.nsNoNull
的返回值从[AnyHashable: Any]
转换为[String: Any]
。 (#25) - 将
JSON.Encoder.encodeAs*
和JSON.Decoder.decode
方法拆分为带options:
和不带options:
的重载对。这使得替换JSONEncoder
/JSONDecoder
方法引用为 PMJSON 中的等效方法变得更容易。 - 添加满足 Combine 的
TopLevelEncoder
和TopLevelDecoder
的兼容,使用Data
作为输入/输出类型。这意味着现在JSON.Encoder.encode(_:)
被标记为弃用,而不是不可用。 - 当变换返回可选值时,将
JSON.flatMap*
和JSONObject.flatMap*
方法重命名为.compactMap*
。(#28) - 将许多方法标记为
@inlinable
。
v3.1.2 (2018-11-06)
- 添加方法
JSONError.withPrefix(_:)
,它通过将前缀添加到路径来返回新的错误。这可以在自定义解析代码中使用,以产生更好的错误,如果现有的便利函数不能满足需求。(#26)
v3.1.1 (2018-05-17)
- 消除 Swift 4.1 警告。
v3.1.0 (2018-02-25)
- 改进空数组/字典的
.pretty
输出。 - 显著加快
JSON.encodeAsData()
的速度。现在它几乎与JSON.encodeAsString()
一样快。 - 加快
JSON.Encoder.encodeAsString()
和JSON.Encoder.encodeAsData()
的速度。 - 向
JSON
添加几个模拟枚举情况的便利静态方法:JSON.int(_:)
和JSON.cgFloat(_:)
。当JSON(_:)
产生过多的类型复杂性时可以使用这些方法。另外,为CGFloat
添加JSON(_:)
覆盖。 - 添加
JSON.Encoder.keyEncodingStrategy
。这非常类似于 Swift 4.1 的JSONEncoder.keyEncodingStrategy
,尽管默认情况下它不会应用于类型为JSON
或JSONObject
的嵌套值(存在另一个选项applyKeyEncodingStrategyToJSONObject
来控制这一点)。 - 添加
JSON.Decoder.keyDecodingStrategy
。这与Swift 4.1的JSONDecoder.keyDecodingStrategy
非常相似,但默认情况下,它不会应用于解码类型为JSON
或JSONObject
的任何值(还有一个选项applyKeyDecodingStrategyToJSONObject
用于控制此行为)。 - 添加
JSON.Encoder.dateEncodingStrategy
。这与JSONEncoder.dateEncodingStrategy
非常相似,但它还包括将ISO8601格式带有分数秒的日期编码成另一种情况的选项(在Apple平台上)。 - 添加
JSON.Decoder.dateDecodingStrategy
。这与JSONDecoder.dateDecodingStrategy
非常相似,但它还包括将ISO8601格式带有分数秒的日期解码成另一种情况的选项(在Apple平台上)。 - 添加
JSON.Encoder.dataEncodingStrategy
。这与JSONEncoder.dataEncodingStrategy
完全相同。 - 添加
JSON.Decoder.dataDecodingStrategy
。这与JSONDecoder.dataDecodingStrategy
完全相同。
v3.0.2 (2018-02-21)
- 添加便利属性
JSONError.path
。 - 添加方法
JSONError.withPrefixedCodingPath(_:)
,以使在Decodable
实现中使用抛出JSONError
的实例方法变得更容易。
v3.0.1 (2018-02-18)
- 修复Swift包管理器支持。
v3.0.0 (2018-02-18)
- 转换为Swift 4。
- 在
JSON
上实现Codable
。 - 添加名为
JSON.Decoder
的Swift.Decoder
实现。 - 添加名为
JSON.Encoder
的Swift.Encoder
实现。
v2.0.3 (2017-09-12)
- 为
Decimal
添加Linux支持(在Swift 3.1及更高版本上)。注意:Swift 3.1中的Decimal支持仍然存在漏洞,我们在Apple平台上获得正确值时所使用的工作方法在Linux上不起作用。可能不应依赖Linux上的正确性,除非Swift修复了其Decimal实现。 - 为解码和编码到/从
Data
添加Linux支持。 - 在错误类型上添加Linux支持
LocalizedError
(主要适用于Swift 3.1及更高版本)。 - 修复使用发布配置编译的过程。
- 支持使用
swift test
运行测试套件。
v2.0.2 (2017-03-06)
- 修复Linux兼容性问题。
v2.0.1 (2017-02-26)
- 添加从
Data
返回JSONParser<AnySequence<UnicodeScalar>>
的方法JSON.parser(for:options:)
。与JSON.decode(_:options:)
类似,此方法可以自动检测UTF-8、UTF-16或UTF-32输入。 - 修复与Swift包管理器的兼容性问题。
v2.0.0 (2017-01-02)
- 在支持平台上实现十进制数字的全面支持。这包括一个新的
JSON
变体.decimal
、任何相关访问器和带有新选项.useDecimals
的完整解析/解码支持。使用此选项,任何原本会解码为Double
的数字将解码为Decimal
。 - 为工作与数组相关的工作添加了一个
forEach
访问器集,类似于现有的map
和flatMap
访问器。
v1.2.1 (2016-10-27)
- 处理UTF-32输入。
- 无BOM时检测UTF-16和UTF-32输入。
- 修复了一个错误,当我们处理UTF-16输入时没有传递解码器选项。
v1.2.0 (2016-10-27)
- 更改向编码器/解码器/解析器提供选项的方式。现在,所有选项都以数组字面量语法的形式提供(类似于
OptionSet
)。旧方法现在已被标记为已弃用。 - 为解码器添加了一个新的深度限制选项,默认值为10,000。
- 根据JSONTestSuite实现一个新测试套件。
- 修复输入流中包含没有前导代理的重尾代理导致崩溃的问题。
- 修复解析形式为
1e-1
或1e+1
的数字不正确的问题。 - 当指定
strict
选项时,停止接受形式为01
或-01
的数字。 - 在解码具有UTF-16 BOM的
Data
时添加对UTF-16的支持。 - 在解码
Data
时跳过UTF-8 BOM(如果有)。
v1.1.0 (2016-10-20)
- 将
Hashable
添加到JSONEvent
和JSONParserError
中。 - 使
JSONParserError
符合CustomNSError
,以便更好地处理Obj-C错误。 - 全面支持JSON流。现在
JSONParser
和JSONDecoder
都可以以流模式操作,新增了一个新类型JSONStreamDecoder
,它是一个JSON值的延迟序列,并且添加了一个便利方法JSON.decodeStream(_:)
。 - 将
JSONEventGenerator
重命名为JSONEventIterator
,将JSONParserGenerator
重命名为JSONParserIterator
。旧名称可供使用(但已弃用),以保持向后兼容性。 - 为
JSONParserError
添加模式匹配支持。现在它应该像任何其他错误一样工作,允许您说例如if case JSONParserError.invalidSyntax = error { … }
。
v1.0.1 (2016-09-15)
- 修复CocoaPods。
v1.0.0 (2016-09-08)
- 支持Swift 3.0。
- 添加基本访问器的setter,以便您可以编写如下代码:
json["foo"].object?["key"] = "bar"
。 - 在桥接到
NSError
时为错误提供本地化描述。 - 向
JSONParser
添加对JSON值流的支持(例如"[1][2]"
)。
v0.9.3 (2016-05-23)
- 在
JSON
和JSONObject
上添加一组便利方法,以便于通过键或索引访问返回的数组:`mapArray(_:_:)`,`mapArrayOrNil(_:_:)`,`flatMapArray(_:_:)`和`flatMapArrayOrNil(_:_:)`。 - 添加一组新的便利
JSON
初始化器。 - 修改
description
和debugDescription
为JSON
和JSONObject
以使其更加有用。description
现在是 JSON 编码字符串。 - 为
JSON
和JSONObject
实现CustomReflectable
。
v0.9.2 (2016-03-04)
- CocoaPods 支持。
v0.9.1 (2016-02-19)
- Linux 支持。
- Swift Package Manager 支持。
- 将 API 中
plist
的实例重命名为ns
。旧名称仍然可用,但已标记为已弃用。 - 支持最新的 Swift 快照(2012-02-08)。
v0.9 (2016-02-12)
初版发布。