swift-sion 可以做 swift-json
能做的所有事情,以及更多:
处理 SION,这是一种支持更多数据类型的 "JSON++"。
在以下格式之间转换:
安全、快速且富有表现力地处理 JSON。 为 Swift 4 和 Swift Package Manager 从头开始完全重写。
import JSON
let json:JSON = ["swift":["safe","fast","expressive"]]
此模块在功能上非常像 SwiftyJSON。 它很好且直观地包装了 JSONSerialization。 但它的实现方式有所不同。
JSON
是 struct
。 此模块的 JSON
是 enum
JSONSerialization.jsonObject
的输出保存在其存储属性中,并在运行时转换其值。 此模块的 JSON
是静态的。 绝对更符合 Swift 的风格。JSON.swift
超过 1,500 行,而此模块的少于 400 行(截至撰写本文时)。 由于它非常紧凑,因此您无需构建框架即可使用它。您可以直接将 JSON 构建为字面量…
let json:JSON = [
"null": nil,
"bool": true,
"int": -42,
"double": 42.195,
"string": "漢字、カタカナ、ひらがなと\"引用符\"の入ったstring😇",
"array": [nil, true, 1, "one", [1], ["one":1]],
"object": [
"null":nil, "bool":false, "number":0, "string":"" ,"array":[], "object":[:]
],
"url":"https://github.com/dankogai/"
]
…或字符串…
let str = """
{
"null": null,
"bool": true,
"int": -42,
"double": 42.195,
"string": "漢字、カタカナ、ひらがなと\\"引用符\\"の入ったstring😇",
"array": [null, true, 1, "one", [1], {"one":1}],
"object": {
"null":null, "bool":false, "number":0, "string":"" ,"array":[], "object":{}
},
"url":"https://github.com/dankogai/"
}
"""
JSON(string:str)
…或 URL 的内容…
JSON(urlString:"https://api.github.com")
…或通过解码 Codable
数据…
import Foundation
struct Point:Hashable, Codable { let (x, y):(Int, Int) }
var data = try JSONEncoder().encode(Point(x:3, y:4))
try JSONDecoder().decode(JSON.self, from:data)
一旦您有了 JSON 对象,转换为其他格式就很简单。
要转换为 JSON 字符串,您只需对其进行字符串化。 .description
或 "(json)" 就足够了。
json.description
"\(json)" // JSON is CustomStringConvertible
如果您需要 Data
,只需调用 .data
。
json.data
如果您想将其(返回)提供给 Foundation
框架,请调用 .jsonObject
let json4plist = json.pick{ !$0.isNull } // remove null
let plistData = try PropertyListSerialization.data (
fromPropertyList:json4plist.jsonObject,
format:.xml,
options:0
)
print(String(data:plistData, encoding:.utf8)!)
一个空白的 JSON 数组非常简单:
var json = JSON([])
您可以像普通数组一样分配元素:
json[0] = nil
json[1] = true
json[2] = 1
请注意,RHS 字面量不是 nil
、true
和 1
,而是 .Null
、.Bool(true)
和 .Number(1)
。 因此,这不起作用:
let one = "one"
json[3] = one // error: cannot assign value of type 'String' to type 'JSON'
在这种情况下,您可以这样做:
json[3].string = one
它们都是 getter 和 setter。
json[1].bool = true
json[2].number = 1
json[3].string = "one"
json[4].array = [1]
json[5].object = ["one":1]
作为 getter,它们是可选的,当类型不匹配时返回 nil
。
json[1].bool // Optional(true)
json[1].number // nil
因此,您可以像这样进行更改:
json[2].number! += 1 // now 2
json[3].string!.removeLast() // now "on"
json[4].array!.append(2) // now [1, 2]
json[5].object!["two"] = 2 // now ["one":1,"two":2]
当您将值分配给索引越界的 JSON 数组时,它会自动拉伸,未分配的元素设置为 null
,就像 ECMAScript Array
一样。
json[10] = false // json[6...9] are null
正如您现在可能已经猜到的那样,一个空白的 JSON 对象(字典)是:
json = JSON([:])
并以直观的方式进行操作,如下所示:
json["null"] = nil // not null
json["bool"] = false
json["number"] = 0
json["string"] = ""
json["array"] = []
json["object"] = [:] // not {}
JSON
是一种递归数据类型。 对于递归数据类型,您需要一种递归方法来深入遍历数据。 为此,JSON
提供了 .pick
和 .walk
。
.pick
是一种 ".deepFilter
",可以递归地过滤。 您已经在上面看到过它。 它接受类型为 (JSON)->Bool
的过滤器函数。 该函数应用于树的所有叶节点值,并且不满足谓词的叶节点将被修剪。
// because property list does not accept null
let json4plist = json.pick{ !$0.isNull }
.walk
是一个 deepMap
,可以递归地转换。 这一项有点困难,因为您必须分别考虑在节点和叶子上做什么。 为了让您的生活更轻松,提供了三个不同版本的 .walk
。 第一个只接受一个叶节点。
// square all numbers and leave anything else
JSON([0,[1,[2,3,[4,5,6]]], true]).walk {
guard let n = $0.number else { return $0 }
return JSON(n * n)
}
第二个形式只接受一个节点。 与其解释它,不如让我向您展示如何通过扩展 JSON
并使用 .select
(它与 .pick
完全相同)来实现 .pick
。
extension JSON {
func select(picker:(JSON)->Bool)->JSON {
return self.walk{ node, pairs, depth in
switch node.type {
case .array:
return .Array(pairs.map{ $0.1 }.filter({ picker($0) }) )
case .object:
var o = [Key:Value]()
pairs.filter{ picker($0.1) }.forEach{ o[$0.0.key!] = $0.1 }
return .Object(o)
default:
return .Error(.notIterable(node.type))
}
}
}
}
最后一个形式同时接受两者。 与之前的形式不同,这一个可以返回除 JSON
之外的其他类型。 这是一个快速而粗略的 .yaml
,用于发出 YAML。
extension JSON {
var yaml:String {
return self.walk(depth:0, collect:{ node, pairs, depth in
let indent = Swift.String(repeating:" ", count:depth)
var result = ""
switch node.type {
case .array:
guard !pairs.isEmpty else { return "[]"}
result = pairs.map{ "- " + $0.1}.map{indent + $0}.joined(separator: "\n")
case .object:
guard !pairs.isEmpty else { return "{}"}
result = pairs.sorted{ $0.0.key! < $1.0.key! }.map{
let k = $0.0.key!
let q = k.rangeOfCharacter(from: .newlines) != nil
return (q ? k.debugDescription : k) + ": " + $0.1
}.map{indent + $0}.joined(separator: "\n")
default:
break // never reaches here
}
return "\n" + result
},visit:{
if $0.isNull { return "~" }
if let s = $0.string {
return s.rangeOfCharacter(from: .newlines) == nil ? s : s.debugDescription
}
return $0.description
})
}
}
JSON
是 Equatable
,因此您可以检查两个 JSON 是否相同。JSON(string:foo) == JSON(urlString:"https://example.com/whereever")
JSON
是 Hashable
,因此您可以将其用作字典键。
JSON
是 ExpressibleBy*Literal
。 这就是为什么您可以使用上面显示的 variable:JSON
构造进行初始化。
JSON
是 CustomStringConvertible
,其 .description
始终是有效的 JSON。
JSON
是 Codable
。 您可以使用此模块代替 JSONEncoder
。
JSON
是 Sequence
。 但是在迭代时,请注意键。
let ja:JSON = [nil, true, 1, "one", [1], ["one":1]]
// wrong!
for v in ja {
//
}
// right!
for (i, v) in ja {
// i is NOT an Integer but KeyType.Index.
// To access its value, say i.index
}
let jo:JSON = [
"null":nil, "bool":false, "number":0, "string":"",
"array":[], "object":[:]
]
for (k, v) in jo {
// k is NOT an Integer but KeyType.Key.
// To access its value, say i.key
}
这是因为 swift 要求返回相同的 Element
类型。 如果您觉得这违反直觉,您可以简单地使用 .array
或 .object
for v in ja.array! {
// ...
}
for (k, v) in jo.object! {
// ...
}
一旦 init
ed,JSON
永远不会失败。 也就是说,它永远不会变成 nil
。 JSON
没有变为可失败或抛出异常,而是具有一个特殊的值 .Error(.ErrorType)
,它会在方法调用中传播。 以下代码检查可能发生的错误。
if let e = json.error {
debugPrint(e.type)
if let nsError = e.nsError {
// do anything with nsError
}
}
$ git clone https://github.com/dankogai/swift-json.git
$ cd swift-json # the following assumes your $PWD is here
$ swift build
简单地
$ scripts/run-repl.sh
或
$ swift build && swift -I.build/debug -L.build/debug -lJSON
然后在您的 repl 中,
1> import JSON
2> let json:JSON = ["swift":["safe","fast","expressive"]]
json: JSON.JSON = Object {
Object = 1 key/value pair {
[0] = {
key = "swift"
value = Array {
Array = 3 values {
[0] = String {
String = "safe"
}
[1] = String {
String = "fast"
}
[2] = String {
String = "expressive"
}
}
}
}
}
}
Xcode 项目特意从存储库中排除,因为它应该通过 swift package generate-xcodeproj
生成。 为了方便起见,您可以
$ scripts/prep-xcode
然后 Workspace 将打开,Playground 位于顶部。 playground 编写为手册。
不幸的是,Swift Package Manager 不支持 iOS。 更糟糕的是,Swift Playgrounds 不支持模块。 但不用担心。 此模块非常紧凑,您只需要复制 JSON.swift。
对于 Swift Playgrounds,只需将其复制到 Sources
文件夹下即可。 如果您太懒,只需运行
$ scripts/ios-prep.sh
iOS/JSON.playground
就全部设置好了。 您无需在其中 import JSON
。
将以下内容添加到 dependencies
部分:
.package(
url: "https://github.com/dankogai/swift-json.git", from: "4.0.0"
)
并将以下内容添加到 .target
参数:
.target(
name: "YourSwiftyPackage",
dependencies: ["JSON"])
现在您所要做的就是:
import JSON
在你的代码中。 尽情享受吧!
Swift 4.1 或更高版本,OS X 或 Linux 用于构建。