在日常开发中,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 的一些总结,希望可以帮助到大家。