对于开发的人员来说,接受后台的数据,并转换成自己的数据模型是常见的事情。但是作为苹果开发者,并没有一个很好的工具去直接转换,必须借助与第三方的开发的库。那么比较好用的有YYModel、MJExtension。当然还有其他的库。但是有个问题,对于第三方库,如果使用过多对于我们开发者来说并不是一件好事。简单分析一下:优点:功能强大,使用方便,提高开发效率,相对较安全出现bug几率较小。缺点:受限于第三方(主要是版本),如果有bug不容易修改,甚至只能官方修改,增大开发包,使用过多容易导致编译速度极慢,实际用到的功能很少。基于以上选择,使不使用第三方自己斟酌,我们这篇文章,在这不做讨论,对于swift而言,我们这篇文章主要讲怎么使用官方的Codable转换成自己的模型。下面进入正题。
swift引入了Codable编码与解码的功能,简单来说就是支持把一个json,转成自己创建并遵从Codable协议的一个数据模型,可以是类,也可以是结构体struct,对于结构体这个是第三方所没有的一个功能。苹果官方推荐使用结构体,方便,安全,占用内存少,还不用担心内存释放问题,好处多多。我们针对struct来说一下。
首先我们先创建一个json数据:
{
"name": "张三",
"age": 20,
"birthday": "1990-06",
"location": "中国",
"job": "教师"
}
然后我们创建一个结构体模型接收这个数据:
struct UserModel: Codable {
var name = "";
var age = 0;
var birthday = "";
var location = "";
var job = "";
}
说明这个结构体模型必须实现Codable协议。
然后我们去进行decode解码:
let dict = """
{
"name": "张三",
"age": 20,
"birthday": "1990-06",
"location": "中国",
"job": "教师"
}
"""
let decode = JSONDecoder();
if let data = dict.data(using: .utf8) {
let item = try? decode.decode(UserModel.self, from: data);
print("item: \(item)");
}
同过打印我们发现,可以正常输出struct的各个值,使用起来就是这么简单。
2.对于复杂的json我们也是可以解析的。比如下面的json:
{
"name": "张三",
"age": 20,
"birthday": "1990-06",
"location": "中国",
"job": "教师",
"students": [
{
"name": "小丽",
"age": 12,
"id": 1234,
"sex": "女"
},
{
"name": "小万",
"age": 11,
"id": 1235,
"sex": "男"
},
{
"name": "小明",
"age": 14,
"id": 1237,
"sex": "男"
}
]
}
上边这个是比较复杂的,一个对象包括了数组,数组里面又有对象,那么对于这个struct模型我们可以这样创建:
struct UserModel: Codable {
var name = "";
var age = 0;
var birthday = "";
var location = "";
var job = "";
var students: [StudentsModel]!
}
struct StudentsModel: Codable {
var name = "";
var age = 0;
var id = 0;
var sex = "";
}
使用起来还是上边的那个方法我们可以看到students打印出来的是个数组。
但是有时候,情况并不我们想象中的那么完美,尤其是在命名规则上,后台有可能会出现带下滑杠的命名方式比如:student_name等形式,而swift一般是驼峰是命名比如:studentName,这就带来了一个问题,这两个显示是不匹配的,那么我们怎么解决这个问题呢,比如我们有如下的json数据:
{
"name": "张三",
"phone_number": 18467898765,
"pic_url": "中国"
}
这个时候我的需要把手动把属性和值对应起来。
struct UserModel: Codable {
var name = "";
var phoneNumber = 0;
var picURL = "";
enum UserKeys: String, CodingKey {
case phoneNumber = "phone_number"
case picURL = "pic_url"
case name = "name"
}
init(from decoder: Decoder) throws{
let value = try? decoder.container(keyedBy: UserKeys.self);
phoneNumber = (try? value?.decode(Int.self, forKey: .phoneNumber)) ?? 0;
picURL = (try? value?.decode(String.self, forKey: .picURL)) ?? "";
name = (try? value?.decode(String.self, forKey: .name)) ?? "";
}
}
从上面也可以看出,当键和值的类型不一样的时候我们也是可以转换的。
swift的Codable很简单也很强大,如果我们在实际开发的时候,后台返回来的数据并不是完整的,如果没有那个值可能就不返回来了,这个时候我们上面的问题就暴露出来了。具体是什么错误呢。我们把数据json的name去掉,并改成如下的形式。
数据:
{
"age": 12,
"birthday": "2000-01"
}
模型:
struct UserModel: Codable {
var name = "";
var age = 0;
var birthday = "";
}
测试方法我们也变一下:
let decode = JSONDecoder();
if let data = stringValue.data(using: .utf8) {
do {
let item = try decode.decode(UserModel.self, from: data);
print("item: \(item)");
} catch {
print("error =\(error)");
}
}
会打印如下的错误信息:
error =keyNotFound(CodingKeys(stringValue: "name", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"name\", intValue: nil) (\"name\").", underlyingError: nil))
就是我们定义的name是必须有的,但是后台没有返回所以就出错了,这个时候我们要定义成可选类型,如下:
struct UserModel: Codable {
var name: String?
var age: Int?
var birthday: String?
}
这个时候就可以放心的使用了,发现成功答应值,只是name是空。
其实我们发现这些属性的值我们使用过程中一般都是读取,为了更好的表达这个意图,我们一般定义成下面这样,保证了数据的安全:
struct UserModel: Codable {
let name: String?
let age: Int?
let birthday: String?
}
为了更好的使用这个Codable这个特性,我们可以封装一下,给JSONDecoder添加一个扩展,具题如下:
extension JSONDecoder {
class func jsonDecoder(_ type: T.Type,from data: Data?) -> T? {
guard let data = data else {
return nil;
}
let json = JSONDecoder();
var jsonItem: T? = nil;
do {
jsonItem = try json.decode(type, from: data)
} catch {
printObject("data convert json error: \(error)");
}
return jsonItem;
}
class func jsonDecoder(_ type: T.Type,fromAny data: Any?) -> T? {
if let data = data as? Data {
return jsonDecoder(type, from: data);
}
guard let data = data else {
return nil;
}
if let stringValue = data as? String {
return jsonDecoder(type, from: stringValue.data(using: .utf8));
}
let jsonData = try? JSONSerialization.data(withJSONObject: data, options: .prettyPrinted);
return jsonDecoder(type, from: jsonData);
}
}
这样使用起来就方便了许多。好了对于Codable的使用我就说这么多,还有编码也是一样的,可以自己探索一下。
还是那句老话:学习的路上,总不缺少努力的人。祝你越来越。