现已存档和 Fork

PMJSON 将不会在这个仓库中继续维护。请使用、创建 issue 并向 位于此处的 PMJSON fork 版本 提交 PR。

PMJSON

Version Platforms Languages License Carthage compatible CocoaPods

PMJSON 提供了一个纯 Swift 强类型 JSON 编码器/解码器,以及一套方便的方法,用于转换为/从 Foundation 对象转换,以及解码 JSON 结构。

整个 JSON 编码器/解码器可以在不依赖 Foundation 框架的情况下使用,只需从项目中移除 ObjectiveC.swiftDecimalNumber.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.EncoderSwift.Decoder 的支持。详情请参阅 此部分

解析

JSON 解码器被分为独立的解析器和解码器阶段。解析器消耗任何 Unicode 标量序列,并生成 JSON “事件” 序列(类似于 SAX XML 解析器)。解码器接受 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 流是多个顶级 JSON 值,带有可选的空白分隔符(例如 {"a": 1}{"a": 2})。使用此功能的最简单方法是使用 JSON.decodeStream(_:),它返回一个 JSONStreamValue 延迟序列,其中包含 JSON 值或 JSONParserError 错误。您也可以直接使用 JSONParserJSONDecoder,以便更精细地控制流处理。

JSONParserJSONDecoder

如上所述,JSON 解码器被分为独立的解析器和解码器阶段。JSONParser 是解析器阶段,它封装了任何 UnicodeScalar 序列,并且本身也是 JSONEvent 的序列。JSONEvent 是 JSON 解析的单个步骤,例如当遇到 { 时的 .objectStart,或者当遇到 "string" 时的 .stringValue(_)。如果您想对 JSON 进行任何类型的延迟处理(例如,如果您正在处理一个巨大的 JSON blob,并且不想一次性将整个内容解码到内存中),您可以直接使用 JSONParser 来发出事件流。

类似地,JSONDecoder 是解码器阶段。它封装了 JSONEvent 的序列,并将该序列解码为正确的 JSON 值。被封装的序列还必须符合一个单独的协议 JSONEventIterator,该协议提供行/列信息,这些信息在发出错误时使用。如果您想封装除 JSONParser 之外的事件序列,或者如果您想要与 JSONStreamDecoder 提供的 JSON 流解码不同的接口,您可以直接使用 JSONDecoder

由于这种分离的性质,您可以轻松地提供您自己的事件流或您自己的解码阶段。或者您可以执行诸如将 JSONParser 包装在适配器中之类的操作,该适配器在将事件传递给解码器之前修改事件(这可能比转换生成的 JSON 值更有效)。

访问器

除了编码/解码之外,此库还提供了一套全面的访问器,用于从 JSON 值中获取数据。提供了 4 种基本类型的访问器

  1. 以类型命名的基本属性访问器,例如 .string。如果值与类型匹配,这些访问器返回底层值,如果值类型不正确,则返回 nil。例如,.string 返回 String?。这些访问器不在类型之间转换,例如 JSON.Int64(42).string 返回 nil
  2. 以单词 as 开头的属性访问器,例如 .asString。这些访问器也返回一个可选值,但如果这样做有意义,它们会在类型之间进行转换。例如,JSON.Int64(42).asString 返回 "42"
  3. get 开头的方法,例如 getString()。这些方法返回非可选值,如果值的类型不匹配,则抛出 JSONError。这些方法不在类型之间转换,例如 try JSON.Int64(42).getString() 会抛出一个错误。对于每种类型的方法,还有一个以 OrNil 结尾的变体,例如 getStringOrNil(),它确实返回一个可选值。这些方法仅在值为 null 时返回 nil,否则它们会抛出一个错误。
  4. to 开头的方法,例如 toString()。这些方法与 get 方法类似,只是它们在适当时在类型之间进行转换,使用与 as 方法相同的规则,例如 try JSON.Int64(42).toString() 返回 "42"。与 get 方法一样,也有以 OrNil 结尾的变体。

JSON 还提供了键控和索引下标运算符,它们返回 JSON?,并且始终可以安全调用(即使索引超出范围)。它还提供了 2 种下标访问器

  1. 对于每个基本的 get 访问器,都有一个变体,它接受键或索引。这些变体等效于对接收器进行下标运算,并在结果上调用 get 访问器,只是它们产生更好的错误(并且它们正确处理丢失的键/超出范围的索引)。例如,getString("key")getString(index)。如果键不存在或索引超出范围,OrNil 变体也会返回 nil
  2. 类似地,to 访问器也有下标等效项。

最后,getObject()getArray() 访问器提供了接受闭包的变体。建议使用这些变体而不是基本访问器,因为它们产生更好的错误。例如,给定以下 JSON

{
  "object": {
    "elements": [
      {
        "name": null
      }
    ]
  }
}

以及以下代码

try json.getObject("object").getArray("elements").getObject(0).getString("name")

此代码抛出的错误将具有描述 "name: expected string, found null"

但给定以下等效代码

try json.getObject("object", { try $0.getArray("elements", { try $0.getObject(0, { try $0.getString("name") }) }) })

此代码抛出的错误将具有描述 "object.elements[0].name: expected string, found null"

所有这些访问器在 JSONObject 类型(表示对象的类型)上也都可用。

上面的最后一个代码片段看起来非常冗长,但在实践中,您最终不会编写这样的代码。相反,您通常最终只会编写类似这样的代码

try json.mapArray("elements", Element.init(json:))

助手函数

JSON 类型具有静态方法 map()flatMap()compactMap(),用于处理数组(因为 PMJSON 没有定义自己的数组类型)。与使用等效的 SequenceType 方法相比,使用这些 PMJSON 静态方法的好处是它们产生更好的错误。

还有一些助手函数用于转换为/从 Foundation 对象转换。JSON 提供了一个初始化器 init(ns: Any) throws,它可以将任何 JSON 兼容的对象转换为 JSONJSONJSONObject 都提供了属性 .ns,它返回一个等效于 JSON 的 Foundation 对象,以及 .nsNoNull,它执行相同的操作,但省略任何 null 值,而不是使用 NSNull

Codable 支持

JSON 类型符合 Codable 协议,因此它可以编码为 Swift.Encoder,并从 Swift.Decoder 解码。这已经针对标准库提供的 JSONEncoderJSONDecoder 进行了测试。由于解码协议的限制,解码 JSON 必须尝试解码多种不同类型的值,因此,编写不佳的 Swift.Decoder 在解码 JSON 时可能会产生令人惊讶的结果。

编码到 JSON.Encoder 和从 JSON.Decoder 解码已优化,以避免不必要的工作。

Swift.EncoderSwift.Decoder

这个库提供了一个名为 JSON.EncoderSwift.Encoder 实现。它可以将任何 Encodable 类型编码为 JSONStringData。它的使用方式类似于 Swift.JSONEncoder(只是目前它没有选项来控制特定类型的编码)。

这个库提供了一个名为 JSON.DecoderSwift.Decoder 实现。它可以从 JSONStringData 解码任何 Decodable 类型。它的使用方式类似于 Swift.JSONDecoder(只是目前它没有选项来控制特定类型的解码)。

性能

测试套件包含一些基本的性能测试。使用 PMJSON 解码约 70KiB 的 JSON 所需时间大约是 NSJSONSerialization 的 2.5-3 倍,尽管我没有用不同的输入分布进行测试,并且这种性能可能特定于测试输入的特性。但是,使用 PMJSON 将相同的 JSON 编码回 Data 实际上更快,大约只需要 NSJSONSerialization 所需时间的 75%。这些基准测试是在 Swift 2.x 中执行的,并且自那时以来数字可能已经发生了变化。

要求

作为框架安装需要最低 iOS 8、OS X 10.9、watchOS 2.0 或 tvOS 9.0。

安装

使用任何机制安装后,您可以通过将 import PMJSON 添加到您的代码来使用它。

Swift Package Manager

可以使用 Swift Package Manager 安装 PMJSON,方法是将其添加到您的 dependencies 列表中

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'

许可证

在以下任一许可证下授权

贡献

除非您明确声明另有说明,否则您为纳入作品而有意提交的任何贡献应获得上述双重许可,且不附加任何额外条款或条件。

版本历史

v4.0.0 (2019-11-14)

v3.1.2 (2018-11-06)

v3.1.1 (2018-05-17)

v3.1.0 (2018-02-25)

v3.0.2 (2018-02-21)

v3.0.1 (2018-02-18)

v3.0.0 (2018-02-18)

v2.0.3 (2017-09-12)

v2.0.2 (2017-03-06)

v2.0.1 (2017-02-26)

v2.0.0 (2017-01-02)

v1.2.1 (2016-10-27)

v1.2.0 (2016-10-27)

v1.1.0 (2016-10-20)

v1.0.1 (2016-09-15)

v1.0.0 (2016-09-08)

v0.9.3 (2016-05-23)

v0.9.2 (2016-03-04)

v0.9.1 (2016-02-19)

v0.9 (2016-02-12)

初始版本发布。