TypedJSON Swift

如果可以,使用 Codable 进行 JSON 解析会非常方便。

有时需要理解未知的 JSON 结构。对于这些情况,需要使用 JSONSerialization 并尝试向下转型为各种类型。不幸的是,这会使用 Any 类型,它无法通过类型系统提供关于预期结构的指导。

结构

TypedJSON 旨在添加一些结构,并通过使用 @dynamicMemberLookup 提供一些便利。一些灵感来自 SE-0195 中定义的 JSON

引入了两个核心类型:JSON.ContainerJSON.Value。它们的简化定义如下:

enum JSON {
    enum Container: Equatable {
        case Array([Value])
        case Dictionary([String: Value])
    }

    enum Value: Equatable {
        case Container(Container)
        case String(String)
        case Number(NSNumber)
        case Bool(Bool)
        case Null
    }
}

这允许使用标准的 Swift 模式匹配来解包 case。这两种类型都支持通过动态成员查找、字符串索引和整数索引进行下标访问。

动态成员查找和字符串下标访问仅对于 JSON.Container.Dictionary case,以及 JSON.Value.Container case(当容器为 .Dictionary case 时)才有意义。整数索引仅对于 JSON.Container.Array case,以及 JSON.Value.Container case(当容器为 .Array case 时)才有意义

如果索引(动态成员、字符串或整数)不存在,下标将返回 nil。如果下标查找没有“意义”(如上定义),也会发生相同的情况。这种行为允许在无需显式模式匹配每个 case 的情况下遍历结构。

编码和解码

这个库在编码时的主要目标是准确地表示有效的 JSON 数据,并且能够以代表输入的方式与解码后的 JSON 数据进行交互。这个目标将指导一些决策。

编码

只能对 JSON.Container 进行编码,因此只有这种类型才有 encoded 方法。

func encoded(options: JSONSerialization.WritingOptions = []) -> Data

options 接受与传递给 JSONSerialization.data(withJSONObject:options:) 相同的选项。

请记住,因为只能编码 JSON.Container,所以 .fragmentsAllowed 选项没有意义。

解码

可以使用 JSON.decode(_:) 方法或直接调用初始化器 JSON.Container(encoded:) 来解码 JSON。

目前无法提供额外的解码选项。

示例

可以解码和读取 JSON,如下所示:

let jsonData = """
{"foo":{"bar":{"baz":"boo"}},"baz":[2]}
""".data(using: .utf8)!

let decoded = try JSON.decode(jsonData)

// traverse structure, only extract if string
guard case .String(let baz) = decoded.foo?.bar?.baz else {
    return
}

print(baz) // "boo"

可以使用 Swift 字面量编写 JSON,以下代码将生成与上述相同的结构:

let json: JSON.Contianer = ["foo":["bar":["baz":"boo"]],"baz":[2]]

// traverse structure, only extract if string
guard case .String(let baz) = json.foo?.bar?.baz else {
    return
}

print(baz) // "boo"