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)》一文中的例子