Swift开发中,处理JSON数据序列化是一项常见使命。因为Swift的类型安全特性,处理相似JSON这样的弱类型数据一直是一个挑战。然而,Swift 4引入了一个令人欣喜的特性,即Codable协议。Codable协议为咱们供给了一种简练的方法来序列化和反序列化JSON数据。

虽然Codable协议在处理大多数状况下表现得很超卓,但它并不能完全满意所有需求。例如,它不支持主动类型转化,也无法友好地处理默认值。

假如咱们能解决这些问题,就能更完美地处理JSON数据了。咱们能够自界说解码器和编码器,以供给更高档的功用。经过自界说解码器,咱们能够完成类型的主动转化,将JSON数据转化为目标类型,而无需手动处理。此外,咱们还能够经过自界说编码器,在编码过程中为特点设置默认值,以确保生成的JSON数据契合预期。

总之,经过充分利用Swift的特性和自界说解码器、编码器,咱们能够更好地处理JSON数据,满意咱们更杂乱的需求。
传送门ObjMapper。

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//活动状况
}

这儿有一个活动,活动现现在有三种状况,到现在为止,一切都很夸姣。有一天,忽然说需要给活动增加已下架的状况,what?

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

用Activity解析上面的JSON就会报错,咱们怎么躲避呢,像下面一样

var status: Status?

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

解决方案

有没有更好的方法来处理上面这两个问题呢?详细代码见ObjMapper,这儿简略描述下怎么运用。

1、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/Dictionary Model
String String,Number类型(包括整数,浮点数),Bool
Number类型(包括整数,浮点数) Number类型,String,Bool
Bool Bool,String,Number类型(包括整数,浮点数)
nil nil,0

2、Model的嵌套

let raw_json = """
{
    "author":{
        "id": 888888,
        "name":"Alex",
        "age":"10"
    },
    "title":"model与json互转",
    "subTitle":"怎么优雅的转化"
}
"""
// Model:
struct Author: Codable{
    @Default<Int.Zero> var id: Int
    @Default<String.Empty> var name: String
    //运用Backed后,假如类型不匹配,则类型会主动转化
    //比方,上面的json中,age是个字符串,咱们界说的模型是Int,
    //那么声明@Backed后,会主动转化成Int类型
    @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: raw_json)
//model to json
let json = article.jsonString
print(article?.jsonString ?? "")

3、自界说类型的可选值

话不多说,上代码

struct Activity: Codable {
    enum Status: Int {
        case start = 1//活动开端
        case processing = 2//活动进行中
        case end = 3//活动完毕
    }
    @Default<String.Empty> var name: String
    var status: Status//活动状况
}

这儿有一个活动,活动现现在有三种状况,到现在为止,一切都很夸姣。有一天,忽然说需要给活动增加已下架的状况,what?

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

用Activity解析上面的JSON就会报错,咱们怎么躲避呢,像下面一样

var status: Status?

答案是no、no、no,因为可选值的解码所表达的是“假如不存在,则置为 nil”,而不是“假如解码失利,则置为 nil”,那就用咱们的Default吧,请看下面代码:

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 func defaultValue() -> Status {
            return Status.unknown
        }
    }
    @Default<String.Empty> var name: String
    ///Step 3:运用Default
    @Default<Status> var status: Status//活动状况
}
//{"name": "元旦迎新活动", "status": 4 }
//Activity将会把status解析成unknown

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

本库现已内置了很多默认值,比方Int.Zero, Bool.True, String.Empty…,假如咱们想为字段设置不一样的默认值,见下面代码:

public extension Int {
    enum One: DefaultValue {
        static func defaultValue() -> Int {
            return 1
        }
    }
}
struct Dog: Codable{
    @Backed var name: String?
    @Default<Int.Zero> var uid: Int
    //假如json中没有age字段或许解析失利,则模型的age被设置成默认值1
    @Default<Int.One> var age: Int
}

5、数组支持

关于数组,能够运用@Backed,@Default来解析

// JSON:
let raw_json = """
{
    "code":0,
    "message":"success",
    "data": [{
        "name": "元旦迎新活动",
        "status": 4
    }]
}
"""
struct Activaty: Codable{
    @Default<String.Empty> var name: String
    @Default<Int.Zero> var status: Int
}
// 假如数组是可选类型,能够运用@Backed
struct Response1: Codable {
    @Default<Int.Zero> var code: Int
    @Default<String.Empty> var message: String
    @Backed var data: [Activaty]?
}
// 为数组,设置默认值,假如数组不存在或许解析错误,则运用默认值
struct Response2: Codable {
    @Default<Int.Zero> var code: Int
    @Default<String.Empty> var message: String
    @Default<Array.Empty> var data: [Activaty]
}
//JSON to model
let rsp1 = Response1.decodeJSON(from: raw_json)
let rsp2 = Response2.decodeJSON(from: raw_json)
//model to json
let json1 = rsp1.jsonString
let json2 = rsp2.jsonString
// print(rsp1?.jsonString ?? "")
// print(rsp2?.jsonString ?? "")

6、设置通用类型

咱们在开发过程中,第一个遇到的json可能是这样的:

// JSON:
{
    "code":0,
    "message":"success",
    "data":[]//这个data能够是任何类型
}

因为data字段的类型不固定,有时候为了统一处理,咱们界说模型能够像下面这样,有枚举类型JsonValue来表明。

struct Response: Codable {
    var code: Int
    var message: String
    var data: JsonValue?
}

假如要取data字段的值,咱们能够这样用data?.intValue或许data?.arrayValue等等,详细运用见源码。

留意:这种关于data是一个简略的model(比方便是一个整形、字符串等等),能够起到事半功倍的效果;假如data是一个大型model,建议还是将data指定为详细类型。

7、假如是从1.0.x升级到2.0版别,修改了DefaultValue协议。假如之前的代码中运用了DefaultValue协议,则会报错,修改如下:

原来为:
///Step 1:让Status遵从DefaultValue协议
enum Status: Int, Codable, DefaultValue {
    case start = 1//活动开端
    ///Step 2:完成DefaultValue协议,指定一个默认值
    static let defaultValue = Status.unknown
}
修改成:
///Step 1:让Status遵从DefaultValue协议
enum Status: Int, Codable, DefaultValue {
    case start = 1//活动开端
    ///Step 2:完成DefaultValue协议,返回一个默认值
    static func defaultValue() -> Status {
        return Status.unknown
    }
}

参阅文档

  1. 用 Codable 协议完成快速 JSON 解析
  2. Swift 4 踩坑之 Codable 协议
  3. 运用 Property Wrapper

不喜勿喷,有问题请留言,欢迎✨✨✨star✨✨✨和PR