Thanks to visit codestin.com
Credit goes to excodable.iwill.im

Skip to the content.

ExCodable 1.0

ExCodable

Swift 5.10 Swift Package Manager Platforms
Build and Test GitHub Releases (latest SemVer) Deploy to CocoaPods Cocoapods
LICENSE GitHub stars @minglq

En | 中文


ExCodable 是一个 Swift 版 JSON-Model 转换工具,现在迎来重要升级,发布 1.0 版本。

「若非必要,勿造轮子」。但显然,我又造了一个,所以它一定是必要的:

ObjC 时代,最好的 JSON-Model 转换非 YYModel 莫属,可惜没有 Swift 版,所以 自己写一个吧

主要特性

struct TestExCodable: ExAutoCodable {
    @ExCodable
    var int: Int = 0
    @ExCodable("string", "s", "nested.nested.string")
    var string: String? = nil
}

上面代码虽少,但足可以体现 ExCodable 的强大。

主要特性:

使用方法

1、ExCodable

目前,使用 ExCodable 定义属性需要使用 var(可以使用 private(set) 避免属性被意外修改),并且要提供默认值。

struct TestStruct: ExAutoCodable {
    @ExCodable private(set)
    var int: Int = 0
    @ExCodable("string") private(set)
    var string: String? = nil
}

2、多个候选 JSON key

struct TestAlternativeKeys: ExAutoCodable {
    @ExCodable("string", "str", "s") private(set)
    var string: String! = nil
}

3、多层嵌套的 JSON key

struct TestNestedKeys: ExAutoCodable {
    @ExCodable("nested.nested.string") private(set)
    var string: String! = nil
}

4、RawRepresentable 类型的 enum

enum TestEnum: Int, Codable {
    case zero = 0, one = 1
}

struct TestStructWithEnum: ExAutoCodable {
    @ExCodable private(set)
    var `enum` = TestEnum.zero
}

RawRepresentable 类型的 enum 需要自定义的 encode/decode。

5、自定义 Encode/Decode

@ExCodable 支持自定义 encodedecode,并且 encoderdecoder 支持使用 subscript 方式访问。

struct TestManualEncodeDecode: ExAutoCodable {
    @ExCodable(encode: { encoder, value in
        encoder["int"] = value <= 0 ? 0 : value
    }, decode: { decoder in
        if let int: Int = decoder["int"], int > 0 {
            return int
        }
        return 0
    }) private(set)
    var int: Int = 0
}

6、自动类型转换

ExCodable 支持灵活的类型转换,兼容多层嵌套的 Optional 类型,例如 Int???。并且这些转换同样适用于 RawRepresentable 类型的属性,自动转换到 RawValue 类型并调用 init(rawValue:)

A、内置支持的类型转换:

B、单个属性的自定义类型转换:

struct TestCustomEncodeDecode: ExAutoCodable {
    @ExCodable(decode: { decoder in
        if let string: String = decoder["string"] {
            return string.count
        }
        return 0
    }) private(set)
    var int: Int = 0
}

C、model 中的自定义类型转换:

struct TestCustomTypeConverter: ExAutoCodable {
    @ExCodable private(set)
    var doubleFromBool: Double? = nil
    @ExCodable private(set)
    var floatFromBool: Double? = nil
}

extension TestCustomTypeConverter: ExCodableDecodingTypeConverter {
    
    public static func decode<T: Decodable, K: CodingKey>(_ container: KeyedDecodingContainer<K>, codingKey: K, as type: T.Type) -> T? {
        
        // for nested optionals, e.g. `var int: Int??? = nil`
        let wrappedType = T?.wrappedType
        
        // decode Double from Bool
        if type is Double.Type || wrappedType is Double.Type {
            if let bool = try? container.decodeIfPresent(Bool.self, forKey: codingKey) {
                return (bool ? 1.0 : 0.0) as? T
            }
        }
        // decode Float from Bool
        else if type is Float.Type || wrappedType is Float.Type {
            if let bool = try? container.decodeIfPresent(Bool.self, forKey: codingKey) {
                return (bool ? 1.0 : 0.0) as? T
            }
        }
        
        return nil
    }
}

D、全局的自定义类型转换:

struct TestCustomGlobalTypeConverter: ExAutoCodable, Equatable {
    @ExCodable private(set)
    var boolFromDouble: Bool? = nil
}

extension ExCodableGlobalDecodingTypeConverter: ExCodableDecodingTypeConverter {
    
    public static func decode<T: Decodable, _K: CodingKey>(_ container: KeyedDecodingContainer<_K>, codingKey: _K, as type: T.Type) -> T? {
        
        // for nested optionals, e.g. `var int: Int??? = nil`
        let wrappedType = T?.wrappedType
        
        // decode Bool from Double
        if type is Bool.Type || wrappedType is Bool.Type {
            if let double = try? container.decodeIfPresent(Double.self, forKey: codingKey) {
                return (double != 0) as? T
            }
        }
        
        return nil
    }
}

7、使用 Subscript 手动 Encode/Decode

对于一个未使用 ExCodable 的类型:

struct TestManualEncodingDecoding {
    let int: Int
    let string: String
}

使用 subscript 手动 encode/decode,要比 Swift 原生语法简单得多:

extension TestManualEncodingDecoding: Codable {
    
    enum Keys: CodingKey {
        case int, string
    }
    
    init(from decoder: Decoder) throws {
        int = decoder[Keys.int] ?? 0
        string = decoder[Keys.string] ?? ""
    }
    func encode(to encoder: Encoder) throws {
        encoder[Keys.int] = int
        encoder[Keys.string] = string
    }
}

8、更灵活的异常处理

ExCodable 默认忽略 JSON-Model 转换时遇到的 EncodingError.invalidValueDecodingError.keyNotFoundDecodingError.valueNotFoundDecodingError.typeMismatch 等错误,出错的属性不处理 —— encode 时跳过、decode 时保持默认值。只有 JSON 数据本身有问题时才会抛出错误。

ExCodable 也支持出错时终止转换,抛出错误:

struct TestNonnullAndThrows: ExAutoCodable {
    @ExCodable(nonnull: true, throws: true) private(set)
    var int: Int! = 0
}

9、class 以及子类

class TestClass: ExAutoCodable {
    
    @ExCodable private(set)
    var int: Int = 0
    @ExCodable private(set)
    var string: String? = nil
    
    required init() {}
    init(int: Int, string: String?) {
        (self.int, self.string) = (int, string)
    }
}

class TestSubclass: TestClass {
    
    @ExCodable private(set)
    var bool: Bool = false
    
    required init() { super.init() }
    required init(int: Int, string: String, bool: Bool) {
        self.bool = bool
        super.init(int: int, string: string)
    }
}

10、类型推断

struct TestStruct: ExAutoCodable, Equatable {
    @ExCodable private(set)
    var int: Int = 0
    @ExCodable private(set)
    var string: String? = nil
}

// 正常的转换
let json = Data(#"{"int":200,"string":"OK"}"#.utf8)
let model = try? TestStruct.decoded(from: json)

// 类型推断
let dict = try? model.encoded() as [String: Any]
let copy = try? dict.decoded() as TestStruct

更多用法参考代码中的单元测试。

安装

Swift Package Manager:

.package(url: "https://github.com/ExCodable/ExCodable", from: "1.0.0")

CocoaPods:

pod 'ExCodable', '~> 1.0.0'

升级

如果你用过 0.x 版本,感谢支持!但是时候升级了,ExCodable 依然保留了旧的 API,从而降低升级的难度。

首先,升级到 1.0 之后可以继续使用废弃的 API —— 快速、工作量小:

struct TestExCodable {
    private(set) var int: Int = 0
    private(set) var string: String?
}

extension TestExCodable: ExCodableDEPRECATED {
    static let keyMapping: [KeyMap<Self>] = [
        KeyMap(\.int, to: "int"),
        KeyMap(\.string, to: "nested.nested.string", "string", "str", "s")
    ]
    init(from decoder: Decoder) throws {
        try decode(from: decoder, with: Self.keyMapping)
    }
}

然后逐步升级到新的语法:

struct TestExCodable: ExAutoCodable {
    @ExCodable private(set)
    var int: Int = 0
    @ExCodable("nested.nested.string", "string", "str", "s") private(set)
    var string: String?
}

未来

Swift 5.9 发布时引入了 Macros,很快我就看到了基于它实现的 MetaCodable,这是目前最科学的「实现方式」。这让我一度想放弃维护 ExCodable,但我还是更喜欢 ExCodable 灵活的「使用方式」

计划未来也使用 Macros 实现重写 ExCodable,在保持目前良好特性的同时,突破 Swift 语法对目前方案的种种限制,敬请期待 —— 不确定多久 🫣

星星

如果你喜欢 ExCodable,欢迎 给个星星 ⭐️ 🤩

致敬

在此,再次,致敬 John Sundell 和 ibireme,Codextended 的非凡创意和 YYModel 的丰富特性给了我的极大的启发!

关于

我是 Míng,使用中遇到任何问题,欢迎 反馈 / [email protected]

开源

代码MIT 协议下开源。