Swift已经有了大量的 JSON 库。为什么是这个呢?
Foundation
框架也不需要。enum
中来强制执行严格的数据类型一致性。因此,不再需要求解神秘的对象 AnyObject
。这个库受到了 SwiftyJSON
、Gloss
和 TidyJSON
的启发。
Iolcus
框架的核心是 JSON
类型。实际上,它只是一个 enum
enum JSON {
case Null
case Boolean(Bool)
case Integer(Int)
case Float(Double)
case String(String )
case Array([JSON])
case Object([String: JSON])
}
JSON
字面量可以通过给某个属性赋一个字面量来创建一个 JSON
实例
import Iolcus
let jsonBoolean: JSON = true
let jsonInteger: JSON = 42
let jsonFloat: JSON = 36.6
let jsonString: JSON = "Lorem ipsum dolor sit amet"
let jsonArray: JSON = [false, 1, 42.0, "3"]
let jsonObject: JSON = [
"boolean" : false,
"integer" : -42,
"string" : "Lorem ipsum"
]
JSON
初始化器还有其他创建 JSON
的方式
// Direcly via enum's case
let jsonLUAE = JSON.Integer(42)
// By encoding JSON from an JSONEncodable-conforming instance
let jsonNotFalse = JSON(encoding: true)
// By copying another JSON's sub-element
let jsonSentence: JSON = ["Lorem", "ipsum", "dolor", "sit", "amet"]
let jsonFirstWord = try! JSON(json: jsonSentence, at: 0) // .String("Lorem")
let jsonPerson: JSON = ["name": "John Doe", "age": 42]
let jsonAge = try! JSON(json: jsonPerson, at: "age") // .Integer(42)
// By deserializing string representation of JSON
let serializedFibonacci = "[1, 1, 2, 3, 5, 8]"
let jsonFibonacci = try! JSON(jsonSerialization: serializedFibonacci)
JSON
尽管 JSON
是 enum
,可以作为标准 Swift 中的 switch
语句或 if/case
模式使用,但使用一组 is...
(isNull
、isBoolean
、isInteger
等)计算属性来检查当前正在处理的特定 JSON
类型可能更容易。
例如
let jsonStuff: JSON = [true, "Ook", 36.6, 999, "Eek"] // Prints:
//
for (_, element) in jsonStuff where element.isNumber { // Float 36.6
print(element.kind, element) // Integer 999
}
JSON
值除了.Null
之外,每个JSON
值都封装了一些基本值(Bool
、Int
、Double
或String
)或一个容器([JSON]
或[String: JSON]
)。可以通过特定的计算属性unwrapped...
(如unwrappedBoolean
、unwrappedInteger
、unwrappedFloat
等)访问被JSON
封装的值。这些属性返回封装的值(如果类型匹配)或nil
(如果类型不匹配)。
例如
let jsonEarth: JSON = [
"isFlat" : false,
"mass" : JSON(encoding: ["value": 5.972e24, "uom": "kg"]),
"radius" : JSON(encoding: ["value": 6371, "uom": "km"])
]
if let earthIsFlat = jsonEarth["isFlat"].unwrappedBoolean { // Prints:
if !earthIsFlat { //
print("Earth is not flat") // Earth is not flat
}
}
if let earthMass = jsonEarth["mass"]["value"].unwrappedString { // Prints nothing because
print("Earth's mass is \(earthMass)") // unwrappedString returns nil
} // for JSON.Float values
除了unwrapped...
集合属性外,还有一组coerced...
属性。这组属性提供了从实际封装的基本值到目标类型的隐式转换。如果这种转换不可能,则结果仍然是nil
。
例如
if let earthMass = jsonEarth["mass"]["value"].coercedString { // Prints:
print("Earth's mass is \(earthMass)") //
} // Earth's mass is 5.972e+24
JSON
容器(即.Array
和.Object
)的元素可以通过下标直接访问
let jsonEarthIsFlat = jsonEarth["isFlat"] // .Boolean(false)
下标可以链式使用
let jsonEarthMass = jsonEarth["mass"]["value"] // .Float(5.972e+24)
下标可以用作赋值
var jsonListOfOrders: JSON = [1000000, 1000001, 1000002] // Prints:
//
jsonListOfOrders[2] = 1000005 // [
jsonListOfOrders[1] = 1000003 // 1000007,
jsonListOfOrders[0] = 1000007 // 1000003,
// 1000005
print(jsonListOfOrders) // ]
除了Int
和String
索引的下标外,还有一种特殊的Void
索引下标[]
var jsonDynamicallyGrownArray: JSON = [] // Prints:
//
jsonDynamicallyGrownArray[].append("start" ) // [
jsonDynamicallyGrownArray[].append(".") // "start",
jsonDynamicallyGrownArray[].append("..") // ".",
jsonDynamicallyGrownArray[].append("...end") // "..",
// "...end"
print(jsonDynamicallyGrownArray) // ]
[]
下标的主要目的是提供一种创建/增长/缩小JSON.Array
的方法
.Array
应用getter时,它会解封装并返回JSON
子元素的底层数组。JSON
类型应用getter时,它将从它创建并返回单个元素的数组。[JSON]
数组,并从中创建JSON.Array
。例如,下面的代码片段将产生与上面的完全相同的结果
var jsonImplicitlyConvertedArray: JSON = “start” // 打印: // jsonImplicitlyConvertedArray[][].append(“.” ) // [ jsonImplicitlyConvertedArray[][].append(“..”) // “start”, jsonImplicitlyConvertedArray[][].append(“…end”) // “.”, // “..”, print(jsonImplicitlyConvertedArray) // “…end” // ]
通过实现JSONEncodable
协议,可以启用自定义类型到JSON
的编码
例如,如果我们有一个名为Book
的结构
struct Book {
let title : String
let pagesCount : Int
let isPaperback : Bool
let authors : [String]
let notes : [String: String]
}
…它可以这样实现JSONEncodable
extension Book: JSONEncodable {
func jsonEncoded() -> JSON {
return [
"title" : title,
"pages" : pagesCount,
"isPaperback" : isPaperback,
"authors" : JSON(encoding: authors),
"notes" : JSON(encoding: notes)
]
}
}
注意:数组
authors
和字典notes
必须通过JSON(encoding: _)
初始化器显式编码到JSON
中。这是因为还没有支持(目前)通过泛型条件协议的实现。尽管如此,它可能出现在Swift 3中。
显然,实现了JSONEncodable
后,我们可以通过调用我们刚刚实现的方法将Book
实例编码为JSON
let book = Book( // Prints:
title : "Dune", //
pagesCount : 896, // {
isPaperback : true, // "title": "Dune",
authors : ["Frank Herbert"], // "isPaperback": true,
notes : [ // "authors": [
"2015-05-23": "Damaged. Handed over to repair.", // "Frank Herbert"
"2015-06-10": "Repaired. Condition is good." // ],
] // "notes": {
) // "2015-05-23": "Damaged. Handed over to repair.",
// "2015-06-10": "Repaired. Condition is good."
var jsonBook = book.jsonEncoded() // },
// "pages": 896
print(jsonBook) // }
…或者可以另用初始化器
let jsonAnotherBook = JSON(encoding: book)
自从Book
成为JSONEncodable
,我们还可以编码包含Book
的数组和字典
struct Library: JSONEncodable {
let books: [Book]
let favorites: [String: Book]
let series: [String: [Book]]
func jsonEncoded() -> JSON {
return [
"books" : JSON(encoding: books),
"favorites" : JSON(encoding: favorites),
"series" : JSON(encoding: series)
]
}
}
通过协议实现,JSON
的解码也成为可能。在这种情况下是JSONDecodable
协议。
以我们的Book
为例
extension Book: JSONDecodable {
init(json: JSON) throws {
title = try json.decode(at: "title")
pagesCount = try json.decode(at: "pages")
isPaperback = try json.decode(at: "isPaperback")
authors = try json.decode(at: "authors")
notes = try json.decode(at: "notes")
}
}
对Library
也是如此
extension Library: JSONDecodable {
init(json: JSON) throws {
books = try json.decode(at: "books")
favorites = try json.decode(at: "favorites")
series = try json.decode(at: "series")
}
}
一旦实现了JSONDecodable
,从JSON
解析新的实例只需调用初始化器即可
let anotherBook = try! Book(json: jsonBook)
应该使用方法jsonSerialized()
来序列化JSON
值
let serializedBook = jsonBook.jsonSerialized()
…以及符合JSONEncodable
协议的实例
let anotherSerializedBook = book.jsonSerialized()
可以使用特殊的初始化器JSON(jsonSerialization: _)
来反序列化JSON
值
注意:理论上,用于反序列化的字符串可能是无效的。因此,可能是
JSON.Error.Deserializing
类型的错误。
do {
let jsonDeserialized = try JSON(jsonSerialization: serializedBook)
}
catch let error as JSON.Error.Deserializing {
// Deserializing JSON failed. Do something about it.
}
对于符合JSONDecodable
协议的类型,有类似形式的初始化器。
注意:反序列化
Book
可能会抛出更多类型的错误。除了JSON.Error.Deserializing
外,还可能抛出JSON.Error.Decoding
和JSON.Error.Subscripting
do {
let yetAnotherBook = try Book(jsonSerialization: serializedBook)
}
catch let error as JSON.Error.Deserializing {
// Something went wrong during deserialization of JSON from input string
}
catch let error as JSON.Error.Decoding {
// Something went wrong while decoding of JSON into target type
}
catch let error as JSON.Error.Subscripting {
// Something went wrong while reading values from container JSON elements
}
JSON
容器JSON
类型(.Object
和.Array
)可以遍历。例如,使用forEach()
方法
let jsonTodoList: JSON = ["Groceries", "Pick up kids", "Dinner"] // Prints:
//
jsonTodoList.forEach { // [0] "Groceries"
print($0, $1) // [1] "Pick up kids"
} // [2] "Dinner"
注意:非容器
JSON
类型同样可以遍历,但只会进行一次迭代。注意:
JSONIndex
是一个有三个情况枚举:.This
用于非容器JSON
,.Index(Int)
用于.Array
,以及.Key(String)
用于.Object
。
自然地,map()
、filter()
和所有适用于序列的其他方法也可以与JSON
一起使用。
let pagerCountIndex = jsonBook.filter { $1 == 896 } // Prints:
.first?.index //
print(pagerCountIndex) // Optional(["pages"])
在标准for
-循环中遍历JSON
只会遍历表面,不会深入嵌套的JSON
子元素。因此,JSON
提供了特殊的方法flatten()
,它会构造一个包含(path: JSONPath, elementalValue: JSON)
元组的数组。这样的数组将只包含基本JSON
值(.Null
、.Boolean
、.Integer
、.Float
和.String
),并且每个这样的值都会附带上一个JSONPath
,它包含从根self
开始到特定基本元素需要遍历的索引。
例如
let dune = Book( // Prints:
title : "Dune", //
pagesCount : 896, // {
isPaperback : true, // "title": "Dune",
authors : ["Frank Herbert"], // "isPaperback": true,
notes : [ // "authors": [
"2015-05-23": "Damaged. Handed over to repair.", // "Frank Herbert"
"2015-06-10": "Repaired. Condition is good." // ],
] // "notes": {
) // "2015-05-23": "Damaged. Handed over to repair.",
// "2015-06-10": "Repaired. Condition is good."
let jsonDune = book.jsonEncoded() // },
// "pages": 896
print(jsonDune) // }
print() //
// jsonDune["title"] == "Dune"
jsonDune.flatten().forEach { // jsonDune["isPaperback"] == true
(path: JSONPath, json: JSON) in // jsonDune["authors"][0] == "Frank Herbert"
// jsonDune["notes"]["2015-05-23"] == "Damaged. Handed over to repair."
print("jsonDune\(path) == \(json)") // jsonDune["notes"]["2015-06-10"] == "Repaired. Condition is good."
} // jsonDune["pages"] == 896
// ["notes"]["2015-05-23"]
注意:
JSONPath
本质上是一个包含JSONIndex
值的数组。它可以由一个JSONIndex
值的序列或一个字面量来构建。它也可以用在索引访问器中。