已被 swift-sion 取代

swift-sion 可以做 swift-json 能做的所有事情,以及更多:

Swift 4.1 MIT LiCENSE build status

swift-json

安全、快速且富有表现力地处理 JSON。 为 Swift 4 和 Swift Package Manager 从头开始完全重写。

概要

import JSON
let json:JSON = ["swift":["safe","fast","expressive"]]

描述

此模块在功能上非常像 SwiftyJSON。 它很好且直观地包装了 JSONSerialization。 但它的实现方式有所不同。

初始化

您可以直接将 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 字面量不是 niltrue1,而是 .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(string:foo) == JSON(urlString:"https://example.com/whereever")
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! {
	// ...
}

错误处理

一旦 inited,JSON 永远不会失败。 也就是说,它永远不会变成 nilJSON 没有变为可失败或抛出异常,而是具有一个特殊的值 .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

REPL

简单地

$ 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

Xcode 项目特意从存储库中排除,因为它应该通过 swift package generate-xcodeproj 生成。 为了方便起见,您可以

$ scripts/prep-xcode

然后 Workspace 将打开,Playground 位于顶部。 playground 编写为手册。

iOS 和 Swift Playground

不幸的是,Swift Package Manager 不支持 iOS。 更糟糕的是,Swift Playgrounds 不支持模块。 但不用担心。 此模块非常紧凑,您只需要复制 JSON.swift

对于 Swift Playgrounds,只需将其复制到 Sources 文件夹下即可。 如果您太懒,只需运行

$ scripts/ios-prep.sh

iOS/JSON.playground 就全部设置好了。 您无需在其中 import JSON

从您的 SwiftPM 管理的项目

将以下内容添加到 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 用于构建。