Iolcus 0.0.14

Iolcus 0.0.14

测试已测试
Lang语言 SwiftSwift
许可证 MIT
发布上次发布2016年6月
SPM支持 SPM

Anton Bronnikov 维护。



Iolcus 0.0.14

  • Anton Bronnikov

Iolcus

Code Climate Issue Count

Swift已经有了大量的 JSON 库。为什么是这个呢?

  • 它使用纯 Swift 和只有 Swift,它将在 Swift 可以工作的地方工作。即使是 Foundation 框架也不需要。
  • 它通过将每个可能的 JSON 情况包装在 enum 中来强制执行严格的数据类型一致性。因此,不再需要求解神秘的对象 AnyObject
  • 它非常重视便利性和可读性。特别是,它提供对嵌套 JSON 子组件的子脚本访问、自定义类型的易于编码和解码、默认序列化支持旨在采用编码协议的类型等。

这个库受到了 SwiftyJSONGlossTidyJSON 的启发。

内容

使用方法

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

尽管 JSONenum,可以作为标准 Swift 中的 switch 语句或 if/case 模式使用,但使用一组 is...isNullisBooleanisInteger 等)计算属性来检查当前正在处理的特定 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值都封装了一些基本值(BoolIntDoubleString)或一个容器([JSON][String: JSON])。可以通过特定的计算属性unwrapped...(如unwrappedBooleanunwrappedIntegerunwrappedFloat等)访问被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)                                     // ]

除了IntString索引的下标外,还有一种特殊的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时,它将从它创建并返回单个元素的数组。
  • setter将接受一个输入的[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.DecodingJSON.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值的序列或一个字面量来构建。它也可以用在索引访问器中。