用来快速替代项目中HandyJSON的Codable库

2,491 阅读7分钟

HollowCodable

CocoaPods Compatible CocoaPods Compatible Platform

HollowCodable 是一个基于Swift使用属性包装器的对Codable协议进行扩展的框架。

  • 使用属性包装器使复杂的可编码序列化变得轻而易举!

原生 JSON Codable 的问题

  1. 不支持自定义某个属性的 coding key,一旦你有这种需求,要么把所有的 coding key 手动实现一遍去修改想要的 coding key,要么就得在 decode 的时候去设置 Decoder,极其不方便。
  2. 不支持忽略掉某些不能 Codable 的属性, 还是需要手动实现 coding key 才行。
  3. 解码的时候不支持多个 coding key 映射同一个属性,以及蛇形转驼峰等。
  4. 不能使用模型的默认值,当 decode 的数据缺失时无法使用定义里的默认值而是 throw 数据缺失错误,这很明显是不合理的。
  5. 不支持简单的类型转换,比如转换 0/1 到 false/true,"123" 到 Int的123 或者反过来,谁又能确保服务端的人员不会失手修改了字段类型导致 app 端故障呢?
  6. 支持Any属性,很多时候首页模型会包含很多子模型,但是子模型又都不一样,这时就需要解析成Any,然后根据模块去确认子模型。

ok, 以上问题在你使用HollowCodable之后统统可以得到解决,不用谢!

特性

  • 基于Codable的扩展,采用属性包装器设计

  • 支持属性键名称修改和多键映射到同一属性

  • JSON映射为Model,Model映射为JSON

  • 支持嵌套对象,支持Any对象

  • 支持nil对象

  • 支持默认值,数组、字典默认值

  • 支持String和整数、浮点数、Bool之间的转换

  • 支持自定义解码器和编码器

  • Immutable: 通过属性包装使其变成只读属性,它可以与其他编码一起使用。

struct YourModel: HollowCodable {
    @Immutable
    var id: Int
    
    @Immutable @BoolCoding
    var bar: Bool?
}
  • IgnoredKey: 将此属性添加到可选属性中,以便在编码或解码时不包含它。
struct YourModel: HollowCodable {
    @IgnoredKey
    var ignorKey: String? = "Condy" // 相当于使用 `lazy`.
    
    lazy var ignorKey2: String? = "Coi"
}
  • DefaultBacked: 为常见类型设置不同的默认值。
    • 这个库内置了许多默认值,如Int, Bool, String, Array...
    • 如果我们想为该字段设置不同的默认值。
struct YourModel: HollowCodable {
    @DefaultBacked<Int>
    var val: Int // 如果该字段不是可选的,则默认为0。
    
    @DefaultBacked<Bool>
    var boo: Bool // 如果该字段不是可选的,则默认为false。
    
    @DefaultBacked<String>
    var string: String // 如果该字段不是可选的,则默认为""。

    @DefaultBacked<AnyDictionary>
    var list: [String: Any] // 如果该字段不是可选的,则默认为[:]。
}

UIColor/NSColor

  • HexColorCoding: 将十六进制字符串颜色转化为UIColor/NSColor属性包装器。
    • 支持十六进制字符串颜色格式有 #RGB#RGBA#RRGGBB#RRGGBBAA
  • RGBColorCoding: 将Red、Green、Blue颜色转化为UIColor/NSColor属性包装器。
  • RGBAColorCoding: 将Red、Green、Blue和Alpha颜色转化为UIColor/NSColor属性包装器。
struct YourModel: HollowCodable {
    @HexColorCoding
    var color: HollowColor?
    
    @RGBColorCoding
    var background_color: HollowColor?
}

Bool

  • BoolCoding: 支持使用BoolIntString来表示布尔值。
    • 大于0则转化为true,小于等于0则转化为false。
    • 这些数据 "true"/"yes"/"y"/"t"/"1"/">0" 则转为true,
    • 这些数据 "false"/"no"/"f"/"n"/"0" 则转为false.
  • FalseBoolCoding: 如果字段不是可选类型,则默认为false。
  • TrueBoolCoding: 如果字段不是可选类型,则默认为true。
struct YourModel: HollowCodable {
    @Immutable @BoolCoding
    var bar: Bool?
    
    @FalseBoolCoding
    var hasD: Bool
    
    @TrueBoolCoding
    var hasT: Bool
}

Data

  • Base64Coding: 对于应序列化为Base64编码字符串的Data属性。
struct YourModel: HollowCodable {
    @Base64Coding
    var base64Data: Data?
}

您可以自定义您自己的数据(反)序列化类型DataValue.

例如:

public enum YourData: DataConverter {
    
    public typealias Value = String
    public typealias FromValue = Data
    public typealias ToValue = String
    
    public static let hasValue: Value = ""
    
    public static func transformToValue(with value: FromValue) -> ToValue? {
        // data to string..
    }
    
    public static func transformFromValue(with value: ToValue) -> FromValue? {
        // string to data..
    }
}

Used:
struct YourModel: HollowCodable {
    AnyBacked<DataValue<YourData>>
    var data: Data?
}

Date

  • ISO8601DateCoding: 将StringTimeInterval值解码为ISO8601日期。
  • RFC2822DateCoding: 将StringTimeInterval值解码为RFC2822日期。
  • RFC3339DateCoding: 将StringTimeInterval值解码为RFC3339日期。
struct YourModel: HollowCodable {
    @ISO8601DateCoding
    var iso8601: Date? // 现在解码到ISO8601日期。
    
    @RFC2822DateCoding
    var data1: Date? // 现在解码到RFC2822日期。
    
    @RFC3339DateCoding
    var data2: Date? // 现在解码到RFC3339日期。
}
  • SecondsSince1970DateCoding: 对于应该序列化为SecondsSince1970的Date时间戳属性。
  • MillisecondsSince1970DateCoding: 对于应该被序列化为msecondssince1970的Date时间戳属性。
struct YourModel: HollowCodable {
    @SecondsSince1970DateCoding
    var timestamp: Date // 现在编码为SecondsSince1970
    
    @MillisecondsSince1970DateCoding
    var timestamp2: Date // 现在编码为MillisecondsSince1970
}

支持使用不同格式的(反)序列化,例如:

struct YourModel: HollowCodable {
    @DateCoding<Hollow.DateFormat.yyyy_mm_dd, Hollow.Timestamp.secondsSince1970>
    var time: Date? // 解码使用`yyyy-MM-dd`格式和编码使用`seconds`时间戳。
}

Enum

  • EnumCoding: 要实现可转换,enum必须符合rawrepresable协议。现在没有什么特别需要做的。
struct YourModel: HollowCodable {
    @EnumCoding<AnimalType>
    var animal: AnimalType? // 
}

enum AnimalType: String {
    case Cat = "cat"
    case Dog = "dog"
    case Bird = "bird"
}

NSDecimalNumber

  • NSDecimalNumberCoding: 可以将StringDoubleFloatCGFloatInt or Int64解码成NSDecimalNumber属性.
struct YourModel: HollowCodable {
    @DecimalNumberCoding
    var amount: NSDecimalNumber?
}

AnyDictionary/AnyDictionaryArray

  • DictionaryCoding: 支持任何[String:Any]带有字典的值属性包装器。
  • ArrayCoding: 支持任何值[String:Any]字典属性包装与数组。
"mixDict": {
    "sub": {
        "amount": "52.9",
    }, 
    "array": [{
        "val": 718,
    }, {
        "val": 911,
    }],
    "opt": null
}

struct YourModel: HollowCodable {
    @AnyBacked<AnyDictionary>
    var mixDict: [String: Any]? // 嵌套支持。
    
    @AnyBacked<AnyDictionaryArray>
    var mixLiat: [[String: Any]]?
}

AnyX

  • AnyXCoding: 支持设置任意类型Any
struct YourModel: HollowCodable {
    @AnyBacked<AnyX>
    var x: Any? // Also nested support.
}

JSON

  • 支持直接解码网络请求响应数据。
{
    "code": 200,
    "message": "test case.",
    "data": [{
        "id": 2,
        "title": "Harbeth Framework",
        "github": "https://github.com/yangKJ/Harbeth",
        "amount": "23.6",
        "hex_color": "#FA6D5B",
        "type": "text1",
        "timestamp" : 590277534,
        "bar": 1,
        "hasDefBool": 2,
        "time": "2024-05-29 23:49:55",
        "iso8601": null,
        "anyString": 5,
        "background_color": {
            "red": 255,
            "green": 128,
            "blue": 128
        },
        "dict": {
            "amount": "326.0"
        },
        "mixDict": {
            "sub": {
                "amount": "52.9",
            }, 
            "array": [{
                "val": 718,
            }, {
                "val": 911,
            }]
        },
        "list": [{
           "fruit": "Apple",
           "dream": null
        }, {
            "fruit": "Banana",
            "dream": "Night"
         }]
    }, {
        "id": 7,
        "title": "Network Framework",
        "github": "https://github.com/yangKJ/RxNetworks",
        "amount": 120.3,
        "hex_color2": "#1AC756",
        "type": null,
        "timestamp" : 590288534,
        "bar": null,
        "hasDefBool": null,
        "time": "2024-05-29 20:23:46",
        "iso8601": "2023-05-23T09:43:38Z",
        "anyString": null,
        "background_color": null,
        "dict": null,
        "mixDict": null,
        "list": null
    }]
}
  • 使用如下:
let datas = ApiResponse<[YourModel]>.deserialize(from: json)?.data
  • Model和JSON互相转换。
json = """
{
     "uid":888888,
     "name": "Condy",
     "age": 18
}
"""

struct YourModel: HollowCodable {
    @Immutable
    var uid: Int?
    
    @DefaultBacked<Int>
    var age: Int // 如果非可选字段,则为0。
    
    @AnyBacked<String>
    var named: String? // 支持多键。
    
    static var codingKeys: [ReplaceKeys] {
        return [
            ReplaceKeys(location: CodingKeys.named, keys: "name", "named"),
        ]
    }
}

// JSON to Model.
let model = YourModel.deserialize(from: json)

// Model to JSON
let json = model.toJSONString(prettyPrint: true)

属性包装器

这边也支持用户自定义,你只需要实现Transformer协议即可。

它还支持可以设置默认值的属性包装器,您需要实现HasDefaultValuable协议即可。

Booming

Booming 是Swift的基础网络库。它是为Swift 5开发的,旨在利用最新的语言特性。该框架的最终目标是实现简单的网络连接,从而使编写易于维护的代码变得容易。

是一套网络架构,基于Moya插件设计使用。

这个模块是序列化和反序列化数据,取代HandyJSON。

🎷 结合网络接口使用如下:

func request(_ count: Int) -> Observable<[CodableModel]> {
    CodableAPI.cache(count)
        .request(callbackQueue: DispatchQueue(label: "request.codable"))
        .deserialized(ApiResponse<[CodableModel]>.self)
        .compactMap({ $0.data })
        .observe(on: MainScheduler.instance)
        .catchAndReturn([])
}
  • RxSwift Codable 扩展。
public extension Observable where Element: Any {

    @discardableResult func deserialized<T>(_ type: T.Type) -> Observable<T> where T: HollowCodable {
        return self.map { element -> T in
            return try T.deserialize(element: element)
        }
    }
    
    @discardableResult func deserialized<T>(_ type: [T].Type) -> Observable<[T]> where T: HollowCodable {
        return self.map { element -> [T] in
            return try [T].deserialize(element: element)
        }
    }
    
    @discardableResult func deserialized<T>(_ type: T.Type) -> Observable<ApiResponse<T.DataType>> where T: HasResponsable, T.DataType: HollowCodable {
        return self.map { element -> ApiResponse<T.DataType> in
            return try T.deserialize(element: element)
        }
    }
    
    @discardableResult func deserialized<T>(_ type: T.Type) -> Observable<ApiResponse<[T.DataType.Element]>> where T: HasResponsable, T.DataType: Collection, T.DataType.Element: HollowCodable {
        return self.map { element -> ApiResponse<[T.DataType.Element]> in
            return try T.deserialize(element: element)
        }
    }
}

最后

  • 关于Codable框架介绍与设计到此为止吧。
  • 慢慢再补充其他相关属性包装器,喜欢就给我点个星🌟吧。
  • Demo地址,目前包含20+种属性包装器。
  • 再附上一个图像滤镜库HabethDemo地址 🎷喜欢的老板们可以点个星🌟

✌️.