Swift JSON 发展史
- 最开始的时候还是使用
NSJSONSerialization
转成字典和数组来使用!- 后来苹果用
Swift
重新实现了JSONSerialization
可以避免用NSArray
和NSDictionary
来桥接,提高解析效率。- 随后很多三方
JSON
库相继出现,例如:SwiftyJSON
、HandyJSON
......等,请原谅我一直没有用过这些三方库,虽然有参考学习过,但我一直维护改进自己封装的JSON库,这期间虽然使用JSON
的简便性有所提高,但我仍然觉得很麻烦。- 从
Swift 4.0
开始,苹果提供了Codable
协议和JSONDecoder
,JSONEncoder
,这一改进使得一众三方库黯然失色,将JSON
到模型的便捷方式提升到了一个新的高度,但这种方式还是过于强硬,模型类型过于死板,稍有不慎就会因为类型不符导致解析失败,因此我将自己的JSON
库添加了Codable
协议支持,作为官方库的一个有益补充。- 在
Swift 4.2
的时候dynamicMemberLookup
(动态属性)的加入又给(软)JSON
模型加了一剂强心针。
例子JSON
{"name":"小王","age":5,"reads":["格林童话","安徒生童话"]}
以前的三方JSON库的缺陷
以前的三方JSON
库在将相对动态的json数据解析到模型的时候除了要写属性类型外,在构造方法中,也要写上对应的key,模型写起来很啰嗦,例如:
struct User {
var name:String
var age:Int
var reads:[String]
init(_ json:JSON) {
name = json["name"].string
age = json["age"].int
reads = json["reads"].array
}
}
复制代码
属性越多,构造方法中写的也越多,相同的键字符串和变量名显得十分啰嗦
JSONDecoder 的缺陷
自从苹果革命性的增加了Codable
协议,现在只需要写模型的属性,而不必写构造方法
struct User: Codable {
var name:String
var age:Int
var reads:[String]
}
复制代码
解析的时候也十分简便
let user = try! JSONDecoder().decode(User.self, from: data)
复制代码
可能是因为苹果作为大公司,有着极度严苛的编码规范和前后端协作标准,因此JSONDecoder
对类型的要求是十分严苛的,如果换成下面这段JSON
内容就会解析失败:
{"name":"小王","age":"5","reads":["格林童话","安徒生童话"]}
这其中将age
的类型变成了字符串的"5"
与模型的Int类型不符,使得整个JSON都无法解析。实际的开发中前后端未必有那么严苛的规范,也不一定总能碰到靠谱的后端,因此经常因为各种类型不符原因解析模型失败就很蛋疼。
可能您会说这只是小问题,只要规范开发就好了,那下面这个问题就是硬伤。
比如说一般情况下后端给我们返回的JSON都有几个固定字段判断返回结果可用性,例如:
{
"errorCode":0,
"errorMessage":"成功",
"result":{}
}
复制代码
{
"errorCode":1,
"errorMessage":"缺少参数",
"result":null
}
复制代码
在上面的例子中errorCode
是服务器返回的错误状态0
表示请求成功errorMessage
是错误状态提示文本,result
根据不同的API接口,返回不同格式的JSON
内容。 如果想写一个模型的话:
struct ResponseJSON: Codable {
var errorMessage: String
var errorCode: Int
var result: ???????
}
复制代码
这就尴尬了,result
属性的类型没法填,因为无法确定此刻它该使用什么模型。 解决方法如下:
- 不使用模型,改用字典,但这样就回归原始,用起来不方便。
result
属性类型使用[String:Any]
,虽然当下使用了模型,但后面使用很不方便。ResponseJSON
使用泛型定义由调用网络请求接口预传入result
所需的模型- 定义一种动态类型保存未完全解析的
JSON
,容许对应接口使用result
字段时懒解析到恰当的模型
其中,比较好的方法是3
和4
, 但方法3还是有缺陷,相同的API返回的JSON
结构就一定相同么?可能有一个type
属性表示着另一个属性是什么结构。这种时候,预传入泛型就无能为力了。
因此我们需要定义一个符合Codable
协议的动态类型
来保存未完全解析的JSON
, 在需要的时候可以懒解析成所需模型。
public enum JSON: Codable {
case object (Object)
case array (Array)
case string (String)
case number (Number)
case bool (Bool)
case null
case error (Error, ignore:[String])
}
复制代码
代码当然不止这点,详细内容可以参考(Basic.frameworks)中关于JSON的部分
- 现在,我们可以将
ResponseJSON
定义成:
struct ResponseJSON: Codable {
var errorMessage: String
var errorCode: Int
var result: JSON
}
复制代码
在需要的时候使用
let user = try! JSON.Decoder().decode(User.self, from: responseJSON.result)
复制代码
或者其他模型
- 前例(
JSON
)中object
、array
、string
、number
、bool
、null
这6个是我们熟知的json数据类型,而error
是什么呢?为什么要添加它?
顾名思义,error
就是错误,添加一个错误类型是用来代替属性获取可选链。
- 如果没有
error
,我们的JSON
应该是这样使用的。
var json:JSON = .....
let name = json.result?.list?[0].name?.string
复制代码
这种方式是利用可选链?
来传递属性,如果其中一个属性不存在,不会报错,而是得到一个nil
值,一般情况这样已经足够,但如果出现错误的json,想要排查问题就需要逐层查看或测试,错误信息在可选链的第几层完全丢失。
现在加入了error (Error, ignore:[String])
类型,就无需使用可选链来传递属性了
var json:JSON = .....
let name = json.result.list[0].name.string
复制代码
当某个属性无法获取时,可以给Error类型,而且还可以传递下去,并且不会丢失错误信息。最终转换string或者int时,可以根据开发环境和生产环境,选择中断或者返回默认值!
JSONDecoder 和 JSON.Decoder 的区别
JSONDecoder
是苹果官方提供的将二进制JSON解析成模型Model
的解析类JSON.Decoder
是(Basic.frameworks)中仿照官方方法,将二进制或JSON软模型解析成模型Model
的解析类
- 他们的作用相似,只是
JSON.Decoder
削弱了数据类型的强制要求,JSON.Decoder
提供了比官方JSONDecoder
更多的解析策略,JSON.Decoder
为了方便提供了全局策略默认值的修改。
流向总结
Data
或String
可以通过官方的JSONDecoder
或(Basic.frameworks)中的JSON.Decoder
解析到实现Codable
协议的Model
模型Data
或String
可以通过(Basic.frameworks)中的JSON.Decoder
或解析到JSON
软模型、Model
模型或两者混合JSON
软模型可以通过(Basic.frameworks)中的JSON.Decoder
解析到Model
模型或混合模型。Model
模型或混合模型可以通过(Basic.frameworks)中的JSON.Encoder
系列化成JSON
软模型,或Data
二进制Model
模型或混合模型可以通过官方的JSONEncoder
系列化成Data
二进制JSON
软模型可以通过官方的JSONEncoder
或(Basic.frameworks)中的JSON.Encoder
系列化成Data
二进制
实际使用实例
参见文章《纯Swift项目-HTTP(Basic.frameworks)》一文中的例子