iOS开发中JSON-Model转化

iOS 开发中经常遇到的数据类型转换发生在网络请求回来的时候,字符串转成 Foudation Object(比如 Dictionary 或者 Array)然后再转换成我们定义好的 Model 类型,一般来说就是 JSON 转 Model。

# Objective-C 时代的解决方案

之前使用 Objective-C 进行开发的时候,OC 并没有提供对 JSON 转化成 Model 很好的支持。我们手动进行转换的时候通常要自己手写一些转换的方法,比如

@interface Student : NSObject
@property(copy) NSString *name;
+ (Student *)fromDict:(NSDictionary *)dict;
@end

@implementation Student
+ (Student *)fromDict:(NSDictionary *)dict {
    Student *student = [Student new];
    student.name = dict[@"name"];
    return student;
}
@end
//完整的转化过程 String -> Foundation Object -> Model
NSString *string = @"{\"name\":\"fanthus\"}";
NSData *jsonData = [string dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:nil];
Student *stu = [Student fromDict:dict];
NSLog(@"name %@",stu.name);

上面的代码里我们是通过 NSJSONSerialization 类将 JSON Data 转化为 Foundation Object的,将 Foundation Object 转化为 JSON Data 的时候也是通过 NSJSONSerialization 这个类,只不过 API 变成了 + (nullable NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error;NSJSONSerialization 本身并没有直接参与到 Model 和 Foundation 的转换过程中。

但是这样进行 Foundation Object 和 Model 的转换就非常的麻烦,每个 Model 都需要实现一遍转换逻辑,非常的麻烦,于是涌现出了很多解决这种问题的库,比如 MJExtension (opens new window)YYModel (opens new window) 等这样库。

# Swift 时代的解决方案

Swift 引入了 Codable 协议,目的就是简化 JSON 和 Model 转换的难度。比如下面这种

struct Student: Codable {
    let name:String
}
//JSON->Model
let json = "{\"name\":\"fanthus\"}"
let student = try JSONDecoder().decode(Student.self, from: json.data(using: .utf8)!)
print(student)
//Model->JSON
let data = try JSONEncoder().encode(student)
let object = try JSONSerialization.jsonObject(with: data)
print(object)

原生的 JSONEncoder 就能直接很方便的进行转化了。我们只需要让我们的 Model 类型遵守 EncodableDecodable 协议。只要 Model 类型的所有属性是 Codable 的,那我们类型就不用做额外的工作,比如 Student 的 name 属性类型是 StringString 类型是 Codable 的,所以 Student 就直接声明遵守 Codable 协议就好了。当然我们自定义类型的属性,只要自定义类型符合 Codable 协议,那类型也是自动遵守 Codable 协议的。

Codable 协议是 EncodableDecodable 的组合 public typealias Codable = Decodable & Encodable

Codable 还引入了 CodingKeys 枚举来实现对某些属性编码忽略以及对编解码用到的别名的支持。CodingKeys 本质上就是一个属性验证列表,包含在内的属性就会被编解码,反之不会。比如下面这种场景:

struct Student: Codable {
    var name:String
	var score:Int = 0   //score 被 CodingKeys 忽略之后需要提供初始值.
    var classroom:Int
    enum CodingKeys: String, CodingKey {
        case name = "name"
        case classroom = "class_room"
    }
}
let json = "{\"name\":\"fanthus\",\"class_room\":1}"
let student = try JSONDecoder().decode(Student.self, from: json.data(using: .utf8)!)
print(student) 
//打印结果: Student(name: "fanthus", score: 0, classroom: 1)

总的来说,原生的 Codable 就能解决我们日常 JSON 转 Model 的场景。

但是有的场景用起来还是不舒服,比如上面 json 字符串里面如果少了 class_room 字段的话,解析就会发生异常了,就算是给 classroom 属性提供默认值也有问题(直接将 classroom 属性设置为 optional 就好了)。所以出现很多新的库来满足更加定制化的转化需求,比如 HandyJSON (opens new window)KakaJSON (opens new window)

我有的项目用的是 HandyJSON,但是 HandyJSON 用到了非常底层的 Swift 语言机制,导致如果系统升级底层有变动的话,就会导致序列化失败,程序崩溃,至今 HandyJSON 的 issue 区还有类似的问题 (opens new window)

综上,想要有完美的解决方案是不行的,感觉 Swift 还是需要再完善一下对 Codable 的支持,让它能兼容更多的场景这样就更好了。

参考地址:

  1. Encoding and Decoding Custom Types (opens new window)
  2. Swift - Codable 使用小记 (opens new window)
  3. 2021 年了,Swift 的 JSON-Model 转换还能有什么新花样 (opens new window)