SmartCodable替換Handjson做Swift json數(shù)據(jù)解析

1. SmartCodable 導包

CocoaPods 安裝

版本 安裝方式 平臺要求 繼承功能支持
基礎版 pod 'SmartCodable' iOS 12+ tvOS 12+ osx10.13+ watchOS 5.0+ visionos 1.0+ ? 否
繼承版 pod 'SmartCodable/Inherit' iOS 13+ macOS 11+ ? 是

?? 重要提示

  • 如果你沒有強烈的繼承需求,推薦使用基礎版

  • 繼承功能需要 Swift 宏支持Xcode 15+Swift 5.9+

2. 基礎用法

遵循 SmartCodable 協(xié)議,類需要實現(xiàn)空初始化器:

class BasicTypes: SmartCodable {
    var int: Int = 2
    var doubleOptional: Double?
    required init() {}
}
let model = BasicTypes.deserialize(from: json)

對于結構體,編譯器會提供默認的空初始化器:

struct BasicTypes: SmartCodable {
    var int: Int = 2
    var doubleOptional: Double?
}
let model = BasicTypes.deserialize(from: json)

1. 多格式輸入支持

輸入類型 使用示例 內部轉換
字典/數(shù)組 Model.deserialize(from: dict或arr) 直接處理原生集合
JSON字符串 Model.deserialize(from: jsonString) 通過UTF-8轉換為Data
二進制數(shù)據(jù) Model.deserialize(from: data) 直接處理

2. 深度路徑解析(designatedPath)

// JSON結構:
{
  "data": {
    "user": {
      "info": { ...目標內容... }
    }
  }
}

// 訪問嵌套數(shù)據(jù):
Model.deserialize(from: json, designatedPath: "data.user.info")

3. 解碼策略(options)

let options: Set<SmartDecodingOption> = [
    .key(.convertFromSnakeCase),
    .date(.iso8601),
    .data(.base64)
]
策略類型 可用選項 描述
鍵解碼 .fromSnakeCase 蛇形命名→駝峰命名
.firstLetterLower "FirstName"→"firstName"
.firstLetterUpper "firstName"→"FirstName"
日期解碼 .iso8601, .secondsSince1970 完整Codable日期策略
數(shù)據(jù)解碼 .base64 二進制數(shù)據(jù)處理
浮點數(shù)解碼 .convertToString, .throw NaN/∞處理

?? 重要: 每種策略類型只允許一個選項(重復時最后一個生效)

2.2 解碼成功后調用的后處理回調

struct Model: SmartCodable {
    var name: String = ""
    mutating func didFinishMapping() {
        name = "我是 \(name)"
    }
}

2.3 鍵轉換

定義解碼時的鍵映射轉換,優(yōu)先使用第一個有效映射:

struct Model: SmartCodable {
    var name: String = ""
    var age: Int?
    
    static func mappingForKey() -> [SmartKeyTransformer]? {
        [
            CodingKeys.name <--- ["nickName", "realName"],
            CodingKeys.age <--- "stu_age",
        ]
    }
}

2.4 值轉換

在JSON值和自定義類型間轉換

內置值轉換器

轉換器 JSON類型 對象類型 描述
SmartDataTransformer String Data Base64字符串和Data對象間轉換
SmartDateTransformer Any Date 處理多種日期格式(時間戳,DateFormat)轉Date對象
SmartURLTransformer String URL 字符串轉URL,可選編碼和添加前綴
struct Model: SmartCodable {
    
    ...
    
    static func mappingForValue() -> [SmartValueTransformer]? {
        let format = DateFormatter()
        format.dateFormat = "yyyy-MM-dd"
        return [
            CodingKeys.url <--- SmartURLTransformer(prefix: "https://"),
            CodingKeys.date1 <--- SmartDateTransformer(strategy: .timestamp),
            CodingKeys.date2 <--- SmartDateTransformer(strategy: .formatted(format))
        ]
    }
}

如果需要額外解析規(guī)則,可以自己實現(xiàn)Transformer。遵循ValueTransformable協(xié)議實現(xiàn)要求:

public protocol ValueTransformable {
    associatedtype Object
    associatedtype JSON
    
    /// 從'json'轉換到'object'
    func transformFromJSON(_ value: Any?) -> Object?
    
    /// 從'object'轉換到'json'
    func transformToJSON(_ value: Object?) -> JSON?
}

內置快速轉換器輔助

static func mappingForValue() -> [SmartValueTransformer]? {
    [
        CodingKeys.name <--- FastTransformer<String, String>(fromJSON: { json in
            "abc"
        }, toJSON: { object in
            "123"
        }),
        CodingKeys.subModel <--- FastTransformer<TestEnum, String>(fromJSON: { json in
            TestEnum.man
        }, toJSON: { object in
            object?.rawValue
        }),
    ]
}

3. 屬性包裝器

通過自定義屬性包裝器,賦予模型屬性更強大的編解碼行為和運行時特性,如類型兼容、鍵忽略、值扁平化、顏色轉換和發(fā)布訂閱等。這些包裝器大大簡化了手動處理 Codable 限制的工作,并提升了模型的表達力與靈活性。

包裝器名 功能簡述
@SmartAny 支持 Any 類型的編碼和解碼,包括 [Any][String: Any]。
@IgnoredKey 忽略屬性的編解碼,等效于不聲明在 CodingKeys 中。
@SmartFlat 將子對象的字段“扁平合并”到當前結構體的字段中進行解碼/編碼。
@SmartHexColor 支持將十六進制字符串自動轉換為顏色對象,如 UIColor / NSColor。
@SmartPublished @Published 屬性自動生成支持 Codable 的 getter/setter 邏輯。

3.2 @IgnoredKey

如果需要忽略屬性解析,可以重寫CodingKeys或使用@IgnoredKey

struct Model: SmartCodable {
    @IgnoredKey
    var name: String = ""
}

let dict: [String: Any] = [
    "name": "Mccc"
]

let model = Model.deserialize(from: dict)
print(model)
// 輸出: Model(name: "")

3.3 @SmartFlat

將結構體屬性的解碼/編碼“扁平化處理”,即:在解析當前對象時,自動將其自身字段合并賦值給被包裝的子對象。

struct Model: SmartCodable {
   var name: String = ""
   var age: Int = 0
 
   @SmartFlat
   var model: FlatModel?
  
}
struct FlatModel: SmartCodable {
   var name: String = ""
   var age: Int = 0
}

let dict: [String: Any] =  [
   "name": "Mccc",
   "age": 18,
]

let model = Model.deserialize(from: dict)
print(model)
// 輸出: Model(name: "Mccc", age: 18, model: FlatModel(name: "Mccc", age: 18))

3.4 @SmartHexColor

struct Model: SmartCodable {
    @SmartHexColor
    var color: UIColor?
}

let dict: [String: Any] = [
    "color": "7DA5E3"
]

let model = Model.deserialize(from: dict)
print(model)
// 輸出: Model(color: UIExtendedSRGBColorSpace 0.490196 0.647059 0.890196 1)

3.5 @SmartPublished

class PublishedModel: ObservableObject, SmartCodable {
    required init() {}
    
    @SmartPublished
    var name: ABC?
}

struct ABC: SmartCodable {
    var a: String = ""
}

if let model = PublishedModel.deserialize(from: dict) {
    // 正確訪問name屬性的Publisher
    model.$name
        .sink { newName in
            print("name屬性發(fā)生變化,新值為: \(newName)")
        }
        .store(in: &cancellables)
}

4. 支持繼承

該功能由于使用了 Swift Macro,需要使用 Swift 5.9+,對應的 iOS 13+,因此只在SmartCodable的5.0+版本中支持。

如需要在更低版本使用繼承,請查看: 低版本中的繼承

如果你需要繼承,請使用 @SmartSubclass 標注為子類。

4.1 基礎使用

class BaseModel: SmartCodable {
    var name: String = ""
    required init() { }
}

@SmartSubclass
class StudentModel: BaseModel {
    var age: Int?
}

4.2 父類實現(xiàn)協(xié)議方法

class BaseModel: SmartCodable {
   var name: String = ""
   required init() { }
   
   static func mappingForKey() -> [SmartKeyTransformer]? {
       [ CodingKeys.name <--- "stu_name" ]
   }
}

@SmartSubclass
class StudentModel: BaseModel {
   var age: Int?
}

4.3 子類實現(xiàn)協(xié)議方法

直接實現(xiàn)即可,不需要 override 修飾。

class BaseModel: SmartCodable {
    var name: String = ""
    required init() { }
    
    class func mappingForKey() -> [SmartKeyTransformer]? {
        retrun nil
    }
}

@SmartSubclass
class StudentModel: BaseModel {
    var age: Int?
    
    override static func mappingForKey() -> [SmartKeyTransformer]? {
        [ CodingKeys.age <--- "stu_age" ]
    }
}

@SmartSubclass
class StudentModel: BaseModel {
var age: Int?

override static func mappingForKey() -> [SmartKeyTransformer]? {
[ CodingKeys.age <--- "stu_age" ]
}
}</pre>

4.4 父子類同時實現(xiàn)協(xié)議方法

需要注意幾點:

  • 父類的類協(xié)議方法需要使用 class 修飾。

  • 子類的類協(xié)議方法需要獲取父類的實現(xiàn)。

class BaseModel: SmartCodable {
    var name: String = ""
    required init() { }
    
    class func mappingForKey() -> [SmartKeyTransformer]? {
        [ CodingKeys.name <--- "stu_name" ]
    }
}

@SmartSubclass
class StudentModel: BaseModel {
    var age: Int?
    
    override static func mappingForKey() -> [SmartKeyTransformer]? {
        let trans = [ CodingKeys.age <--- "stu_age" ]
        
        if let superTrans = super.mappingForKey() {
            return trans + superTrans
        } else {
            return trans
        }
    }
}

5. 特殊支持

5.1 支持枚舉

要使枚舉可轉換,必須遵循SmartCaseDefaultable協(xié)議:

struct Student: SmartCodable {
    var name: String = ""
    var sex: Sex = .man

    enum Sex: String, SmartCaseDefaultable {
        case man = "man"
        case woman = "woman"
    }
}
let model = Student.deserialize(from: json)

要支持 關聯(lián)值枚舉解碼 使枚舉遵循SmartAssociatedEnumerable,重寫mappingForValue方法接管解碼過程:

struct Model: SmartCodable {
    var sex: Sex = .man
    static func mappingForValue() -> [SmartValueTransformer]? {
        [
            CodingKeys.sex <--- RelationEnumTranformer()
        ]
    }
}

enum Sex: SmartAssociatedEnumerable {    
    case man
    case women
    case other(String)
}

struct RelationEnumTranformer: ValueTransformable {
    typealias Object = Sex
    typealias JSON = String

    func transformToJSON(_ value: Sex?) -> String? {
        // 自定義處理
    }
    func transformFromJSON(_ value: Any?) -> Sex? {
        // 自定義處理
    }
}

5.2 字符串JSON解析

SmartCodable在解碼時自動處理字符串化的JSON值,無縫轉換為嵌套模型對象或數(shù)組,同時保持所有鍵映射規(guī)則:

  • 自動解析:檢測并解碼字符串化JSON("{\"key\":value}")為適當對象/數(shù)組

  • 遞歸映射:對解析的嵌套結構應用mappingForKey()規(guī)則

  • 類型推斷:根據(jù)屬性類型確定解析策略(對象/數(shù)組)

struct Model: SmartCodable {
    var hobby: Hobby?
    var hobbys: [Hobby]?
}

struct Hobby: SmartCodable {
    var name: String = ""
}

let dict: [String: Any] = [
    "hobby": "{\"name\":\"sleep1\"}",
    "hobbys": "[{\"name\":\"sleep2\"}]",
]

guard let model = Model.deserialize(from: dict) else { return }

5.3 兼容性

當屬性解析失敗時,SmartCodable會對拋出的異常進行兼容處理,確保整個解析過程不會中斷:

let dict = [
    "number1": "123",
    "number2": "Mccc",
    "number3": "Mccc"
]

struct Model: SmartCodable {
    var number1: Int?
    var number2: Int?
    var number3: Int = 1
}

// 解碼結果
// Model(number1: 123, number2: nil, number3: 1)

類型轉換兼容性

當數(shù)據(jù)類型不匹配時(引發(fā).typeMismatch錯誤),SmartCodable會嘗試將String類型數(shù)據(jù)轉換為所需的Int類型。

默認值填充兼容性

當類型轉換失敗時,會獲取當前解析屬性的初始化值進行填充。

5.4 更新現(xiàn)有模型

可適應任何數(shù)據(jù)結構,包括嵌套數(shù)組結構:

struct Model: SmartCodable {
    var name: String = ""
    var age: Int = 0
}

var dic1: [String : Any] = [
    "name": "mccc",
    "age": 10
]
let dic2: [String : Any] = [
    "age": 200
]
guard var model = Model.deserialize(from: dic1) else { return }
SmartUpdater.update(&model, from: dic2)

// 現(xiàn)在: model是 ["name": mccc, "age": 200].

5.5 解析超大體積數(shù)據(jù)

當解析超大體積數(shù)據(jù)時,盡量避免解析異常的兼容處理,例如:屬性中聲明了多個屬性,且聲明的屬性類型不匹配。

不需要解析的屬性不要使用@IgnoredKey,而是重寫CodingKeys來忽略不需要解析的屬性。

這樣可以大幅提高解析效率。

哨兵系統(tǒng)(Sentinel)

SmartCodable集成了Smart Sentinel,它會監(jiān)聽整個解析過程。解析完成后,會顯示格式化的日志信息。

這些信息僅作為輔助信息幫助您發(fā)現(xiàn)和糾正問題,并不意味著解析失敗。

================================  [Smart Sentinel]  ================================
Array<SomeModel> ???? ??
   ╆━ Index 0
      ┆┄ a: Expected to decode 'Int' but found ‘String’ instead.
      ┆┄ b: Expected to decode 'Int' but found ’Array‘ instead.
      ┆┄ c: No value associated with key.
      ╆━ sub: SubModel
         ┆┄ sub_a: No value associated with key.
         ┆┄ sub_b: No value associated with key.
         ┆┄ sub_c: No value associated with key.
      ╆━ sub2s: [SubTwoModel]
         ╆━ Index 0
            ┆┄ sub2_a: No value associated with key.
            ┆┄ sub2_b: No value associated with key.
            ┆┄ sub2_c: No value associated with key.
         ╆━ Index 1
            ┆┄ sub2_a: Expected to decode 'Int' but found ’Array‘ instead.
   ╆━ Index 1
      ┆┄ a: No value associated with key.
      ┆┄ b: Expected to decode 'Int' but found ‘String’ instead.
      ┆┄ c: Expected to decode 'Int' but found ’Array‘ instead.
      ╆━ sub: SubModel
         ┆┄ sub_a: Expected to decode 'Int' but found ‘String’ instead.
      ╆━ sub2s: [SubTwoModel]
         ╆━ Index 0
            ┆┄ sub2_a: Expected to decode 'Int' but found ‘String’ instead.
         ╆━ Index 1
            ┆┄ sub2_a: Expected to decode 'Int' but found 'null' instead.
====================================================================================

如需使用,請開啟:

SmartSentinel.debugMode = .verbose
public enum Level: Int {
    case none
    case verbose
    case alert
}

如需將日志上傳到服務器:

SmartSentinel.onLogGenerated { logs in  }
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容