Babel
JSON!纯 Swift,驱动失败的,推导的但清晰的,带有强大但可选的操作符。
是什么?
核心
Value
– 表示 Swift ~原始值和/或那些值的字典/数组的递归结构。
JSON
– 使用 JSON 数据(字符串或字节数组)初始化 Value
。
解码
– 简单地遍历 Value
树,并在过程中期望某些类型(或可转换为类型),具有明确的/抛出失败的case。
可选类型
Decodable
– 描述可以从 Value
解码的类型方便的协议;Swift ~原始类型的 Decodable
扩展;扩展将 Value
的数组/字典解码为 Decodable
类型的数组/字典。
操作符
– 利用 解码
和 Decodable
强大功能的简洁操作符。
Foundation
– 为常见的 Foundation 类型提供 Decodable
扩展;使用 NSData 形式的 JSON 初始化 Value
;非纯 Swift/依赖于 Foundation ;)。
辅助工具
- 一些方便的调试或在游乐场中玩耍的工具。
为什么(我们还没有42个这些东西)?
在强类型语言中处理JSON或其他字符串类型的数据,是一项既繁琐又常见的任务。人们往往愿意尝试直到感觉合适。我已经尝试过许多其他解决方案,有的喜欢,有的讨厌,我觉得把喜欢的属性聚集在一起放在一起是有价值的。典型吗?
话虽如此,我相信Babel可以超越其大多数被盗部分的总和。主要目标/目的是提出一个解决方案,在尊重数据模型语义的同时,解码字符串类型的数据。有时缺失的值可以接受,有时不行,有时空值(null)可以接受,有时不行。有时你有小数,但它实际上是以字符串的形式存储/传递的。有时你需要解析整个数组,有时可以忽略解码失败的项。这些类型的事情应该简单明了地描述,以便我们能回到好的编码中——无论那是什么。
最后,一个例子
首先,请在浏览完以下内容后玩玩 Xcode/Babel.playground
(这部分是从这里切出来的)。在具有类型推理的语言的排版中能传达的内容是有限的……该死的Swift,你这么漂亮的伪代码外观真美!
import Babel
let jsonString = "<JSON HERE, SEE PLAYGROUND>"
let jsonData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)!
let value: Value
switch dataExample {
case .String: value = try! Value(JSON: jsonString)
case .Data: value = try! Value(JSON: jsonData)
case .LiteralConvertible:
value = [
"apiVersion": "2.0",
"data": [
"items": [
[
"title": "Google Developers Day US - Maps API Introduction",
"content": [
"5": "http://www.youtube.com/v/hYB0mn5zh2c?f...",
"6": "rtsp://v1.cache1.c.youtube.com/CiILENy.../0/0/0/video.3gp",
"1": "rtsp://v5.cache3.c.youtube.com/CiILENy.../0/0/0/video.3gp"
],
"favoriteCount": 201,
"rating": 4.63,
"uploaded": "2007-06-05T22:07:03.000Z"
],
....
],
"totalItems": 800
]
]
case .NSObject:
value = try! Value(NSObject: NSJSONSerialization.JSONObjectWithData(jsonData, options: .AllowFragments))
}
以下的所有解码示例在语义上是等价的(对于给定的数据模型,它们以相同的方式导航/解析/成功/失败)。你更喜欢哪个(你不一定喜欢自定义运算符)?
do {
let content: [Int: NSURL]?
switch decodingExample {
case .Operators:
content = try value =>? "data" => "items" =>?? 0 => "content"
case .FunctionInferred:
content = try value.maybeValueFor("data")?.valueFor("items")
.maybeValueAt(0, throwOnMissing: false)?.valueFor("content").decode()
case .FunctionExplicit:
content = try value.asDictionary()
.maybeValueFor("data", nilOnNull: true, throwOnMissing: true)?.asDictionary()
.valueFor("items").asArray()
.maybeValueAt(0, nilOnNull: true, throwOnMissing: false)?.asDictionary()
.valueFor("content").asDictionary()
.decode(keyType: Int.self, valueType: NSURL.self, ignoreFailures: false)
case .UnwrappingAndChecking:
var decodedContent: [Int: NSURL]?
if let valueDictionary = value.dictionaryValue {
if let data = valueDictionary["data"] {
if data.isNull {
decodedContent = nil
} else if let dataDictionary = data.dictionaryValue {
if let items = dataDictionary["items"] {
if let itemsArray = items.arrayValue {
if itemsArray.count > 0 && itemsArray[0].isDictionary {
let itemDictionary = itemsArray[0].dictionaryValue!
if let content = itemDictionary["content"] {
if let contentDictionary = content.dictionaryValue {
decodedContent = [:]
for (key, value) in contentDictionary {
if let key = Int(key) {
if let string = value.stringValue, url = NSURL(string: string) {
decodedContent![key] = url
} else { throw DecodingError.TypeMismatch(expectedType: NSURL.self, value: value) }
} else { throw DecodingError.TypeMismatch(expectedType: Int.self, value: .String(key)) }
}
} else { throw DecodingError.TypeMismatch(expectedType: Dictionary<String, Value>.self, value: content) }
} else { throw DecodingError.MissingKey(key: "content", dictionary: itemDictionary) }
} else { decodedContent = nil }
} else { throw DecodingError.TypeMismatch(expectedType: Array<Value>.self, value: items) }
} else { throw DecodingError.MissingKey(key: "items", dictionary: dataDictionary) }
} else { throw DecodingError.TypeMismatch(expectedType: Dictionary<String, Value>.self, value: data) }
} else { throw DecodingError.MissingKey(key: "data", dictionary: valueDictionary) }
} else { throw DecodingError.TypeMismatch(expectedType: Dictionary<String, Value>.self, value: value) }
content = decodedContent
}
prettyPrint("Nested content: ", content)
} catch let error { prettyPrint("Nested content error: ", error) }