关于 Swift Codable 的一些实践

2,176 阅读3分钟

在日常开发中,JSON 转模型使用三方库习惯了,扎一使用系统的 Decodable 协议还有点不习惯。本文主要记录关于 Decodable 的基本使用及特殊情况的处理。

PS: Decodable 还是挺好用的。

JSON 转 struct

单个 struct

先说一下最常碰见的场景:JSON 转 struct。假设有以下代表优惠券类型的 CouponsType:

struct CouponsType {
    let displayCN: String
}

很简单,它是包含一个表示优惠券类型名称的一个字段,来看一下如何使用 Decodable 来解析 JSON。

struct CouponsType: Decodable {
    let displayCN: String
}

let jsonStr = """
                {
                    "displayCN": "全店满减"
                }
                """
                
let type = try? JSONDecoder().decode(CouponsType.self, from: jsonStr.data(using: .utf8)!)
print(type?.displayCN) // Optional("全店满减")

上面的代码做了以下 2 件事:

  • 使 CouponsType 遵守 Decodable
  • 使用 JSONDecoder 将 JSON 字符串解析为 CouponsType

数组

若需要解析数组,是需要将 CouponsType.self 改为 [CouponsType].self 即可。

let jsonStr = """
                [{
                  "displayCN": "全店满减"
                },
                {
                  "displayCN": "全店满赠"
                }]
                """

struct CouponsType: Decodable {
    let displayCN: String
}

let types = try? JSONDecoder().decode([CouponsType].self, from: jsonStr.data(using: .utf8)!)
print(types?.first?.displayCN) // Optional("全店满减")

基本使用还是非常简洁明了的,下面来看一下特殊情况的处理。

特殊情况1:嵌套枚举

在日常开发中,定义的模型经常会嵌套枚举来表示不同类型。假设在上面例子的基础上,我们添加了一个 typeCode 的字段代表优惠券类型的 code,那么该如何解析呢?

嵌套枚举的 CouponsType:

struct CouponsType: Decodable {
    enum CouponsTypeCode: String, Decodable {
        case BP, CP, PLP, UDF
    }

    let displayCN: String
    let typeCode: CouponsTypeCode
}

解析代码:

let jsonStr = """
                {
                    "displayCN": "全店满减",
                    "typeCode": "UDF"
                }
                """

let type = try? JSONDecoder().decode(CouponsType.self, from: jsonStr.data(using: .utf8)!)
print(type?.typeCode.rawValue) //  Optional("UDF")

代码解释:

  • 在 CouponsType 内嵌枚举 - CouponsTypeCode,并使其枚举遵守 Decodable
  • 将 typeCode 类型改为 CouponsTypeCode

看到上面的代码,大家可能觉得大写的 case 并不符合 Swift 的命名规范,对于这种情况,可以让枚举遵守 CodingKey 协议,通过自定义 case 来避免,更新后的 CouponsTypeCode:

// 通过清晰的命名使代码可读性更高
enum CouponsTypeCode: String, Decodable, CodingKey {
     case fullReduce = "BP"
     case fullGift = "CP"
     case limitDiscount = "PLP"
     case custom = "UDF"
}

若是嵌套 struct,只需将嵌套的 struct 遵守 Decodable 即可。

特殊情况2:不存在的字段

通常,在定义模型的时候,难免会定义一些本地字段或者服务器没有返回的字段,这种情况下该如何处理呢?

只需将不包含的字段声明为 Optional 即可:

struct CouponsType1: Decodable {
    enum CouponsTypeCode: String, Decodable, CodingKey {
        case fullReduce = "BP"
        case fullGift = "CP"
        case limitDiscount = "PLP"
        case custom = "UDF"
    }

    let displayCN: String
    let typeCode: CouponsTypeCode
    
    // 本地字段声明为 Optional
    var textColorStr: String?
    var isHiddenImg: Bool?
}

Tip:这一步千万不可忘记,若未将 JSON 串中不包含的字段声明为 Optional,则会解析失败。当时我就在这浪费了好多时间😭

特殊情况3:不一致的类型

在解析数据的时候,我们需保证声明字段的类型和 JSON 串中的类型一致,否则也会解析不成功。

例如在 JSON 中添加一个表示订单号的 orderNum 字符串类型的字段:

let jsonStr = """
                {
                    "displayCN": "全店满减",
                    "typeCode": "UDF",
                    "orderNum": "0",
                }
                """
                
struct CouponsType1: Decodable {
    enum CouponsTypeCode: String, Decodable, CodingKey {
        case fullReduce = "BP"
        case fullGift = "CP"
        case limitDiscount = "PLP"
        case custom = "UDF"
    }

    let displayCN: String
    let typeCode: CouponsTypeCode
    // 此处改为 String 即可解析成功
    let orderNum: Int
    
    
    // local
    var textColorStr: String?
    var isHiddenImg: Bool?
}

let type = try? JSONDecoder().decode(CouponsType.self, from: jsonStr.data(using: .utf8)!)
print(type?.typeCode.rawValue)  // nil

上述代码会解析失败,因为 orderNum 的类型和 JSON 中的类型不一致。只需将其类型改为 String 即可解析成功。

特殊情况4:下换线

假设接口返回的数据字段为下划线风格的,如下:

let jsonStr = """
                {
                  "displayCN": "自定义(其他)",
                  "type_code": "UDF"
                }
                """

我们可以通过指定 JSONDecoder 的 keyDecodingStrategy 属性来通过驼峰规则来解析:

struct CouponsType: Decodable {
    enum CouponsTypeCode: String, Decodable, CodingKey {
        case fullReduce = "BP"
        case fullGift = "CP"
        case limitDiscount = "PLP"
        case custom = "UDF"
    }
    
    let displayCN: String
    let typeCode: CouponsTypeCode
}

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

let types = try? decoder.decode(CouponsType.self, from: jsonStr.data(using: .utf8)!)
print(types?.displayCN) // Optional("自定义(其他)")

以上,就是我在使用 Codable 的一些总结,希望可以帮助到大家。