如何优雅的使用Swift Codable协议

·  阅读 1072

在Swift开发中,JSON数据序列化是一个避不开的工作,Swift由于类型安全的特性,对于像JSON这类弱类型的数据处理一直是一个比较头疼的问题,Swift 4 带来的新特性中, Codable 协议让人眼前一亮。

但是, Codable也不能完全满足我们的要求,比如不支持类型的自动转换、对默认值支持不友好。 so,我们如果把这些问题解决了,是不是就完美啦

Codable坑点1:不支持类型转换

// JSON:
{
    "uid":"123456",
    "name":"Harry",
    "age":10
}

// Model:
struct Dog: Codable{
    var uid: Int
    var name: String?
    var age: Int?
}
复制代码
复制代码

在json转换过程中,我们常常与遇到类型模型与json的类型不一致的情况,就像上面的uid字段,uid在json中是String,但是我们的模型是Int,由于swift是类型安全的,所以,转换就不会成功。

Codable坑点2:不支持默认值

话不多说,上代码

struct Activity: Codable {
    enum Status: Int {
        case start = 1//活动开始
        case processing = 2//活动进行中
        case end = 3//活动结束
    }

    var name: String
    var status: Status//活动状态
}
复制代码

如果你正在面试,或者正准备跳槽,不妨看看我精心总结的面试资料:gitee.com/Mcci7/i-ose… 来获取一份详细的大厂面试资料 为你的跳槽加薪多一份保障

这儿有一个活动,活动现目前有三种状态,到目前为止,一切都很美好。有一天,突然说需要给活动添加已下架的状态,what?

//JSON
{
    "name": "元旦迎新活动",
    "status": 4
}
复制代码

用Activity解析上面的JSON就会报错,我们如何规避呢,像下面一样

var status: Status?
复制代码

答案是no、no、no,因为可选值的解码所表达的是“如果不存在,则置为 nil”,而不是“如果解码失败,则置为 nil”。

解决方案

有没有更好的方式来处理上面这两个问题呢?答案是使用 property wrapper。具体代码见ObjMapper,这儿简单描述下如何使用。

Model与JSON相互转换

// JSON:
{
    "uid":888888,
    "name":"Tom",
    "age":10
}

// Model:
struct Dog: Codable{
    //如果字段不是可选类型,则使用Default,提供一个默认值,像下面一样
    @Default<Int.Zero> var uid: Int
    //如果是可选类型,则使用Backed
    @Backed var name: String?
    @Backed var age: Int?
}

//JSON to model
let dog = Dog.decodeJSON(from: json)

//model to json
let json = dog.jsonString
复制代码

当 JSON/Dictionary 中的对象类型与 Model 属性不一致时,ObjMapper 将会进行如下自动转换。自动转换不支持的值将会被设置为nil或者默认值。

JSON/DictionaryModel
StringString,Number类型(包含整数,浮点数),Bool
Number类型(包含整数,浮点数)Number类型,String,Bool
BoolBool,String,Number类型(包含整数,浮点数)
nilnil,0

Model的嵌套

{
  "author": {
    "id": 888888,
    "name": "Alex",
    "age": "10"
  },
  "title": "model与json互转",
  "subTitle": "如何优雅的转换"
}

// Model:
struct Author: Codable{
    @Default<Int.Zero> var uid: Int
    @Default<String.Empty> var name: String
    //使用Backed后,如果类型不匹配,则会类型转换
    @Backed var age: Int?
}

struct Article: Codable {
    //如果json中的title为nil或者不存在,则会给title赋一个默认值
    @Default<String.Empty> var title: String
    var subTitle: String?
    var author: Author
}

//JSON to model
let article = Article.decodeJSON(from: json)

//model to json
let json = article.jsonString
复制代码

类型的默认值

struct Activity: Codable {
    ///Step 1:让Status遵循DefaultValue协议
    enum Status: Int, Codable, DefaultValue {
        case start = 1//活动开始
        case processing = 2//活动进行中
        case end = 3//活动结束
        case unknown = 0//默认值,无意义

        ///Step 2:实现DefaultValue协议,指定一个默认
        static let defaultValue = Status.unknown
    }

    @Default<String.Empty> var name: String
    ///Step 3:使用Default
    @Default<Status> var status: Status//活动状态
}

///对可选类型值的支持
let json = """
{
    "name": "元旦迎新活动",
    "status": 4
}
"""
///可选类型自动转换
let activity = Activity.decodeJSON(from: json)!
///activity的status,转换为unknown
print("activity.status: \(activity.status)")
///
print("json:\(activity.jsonString ?? "")")
复制代码

为普通类型设置不一样的默认值

ObjMapper已经内置了很多默认值,比如Int.Zero, Bool.True, String.Empty...,如果我们想为字段设置不一样的默认值,见下面代码:

public extension Int {
    enum One: DefaultValue {
        public static let defaultValue = 1
    }
}

struct Dog: Codable{
    @Backed var name: String?
    @Default<Int.Zero> var uid: Int
    //如果json中没有age字段或者解析失败,则模型的age被设置成默认值1
    @Default<Int.One> var age: Int
}
复制代码

参考文档

  1. 用 Codable 协议实现快速 JSON 解析
  2. Swift 4 踩坑之 Codable 协议
  3. 使用 Property Wrapper 为 Codable 解码设定默认值

原文链接:如何优雅的使用Swift Codable协议

分类:
iOS
标签:
分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改