Swift 5.0 Swift Package Manager Platforms Build and Test GitHub Releases (latest SemVer) Deploy to CocoaPods Cocoapods LICENSE @minglq
En | 中文
- Extends Swift
Codable-Encodable & Decodable; - Supports Key-Mapping via
KeyPathand Coding-Key:ExCodabledid not read/write memory via unsafe pointers;- No need to encode/decode properties one by one;
- Just requires using
varto declare properties and provide default values; - In most cases, the
CodingKeytype is no longer necessary, because it will only be used once,Stringliterals may be better.
- Supports multiple Key-Mappings for different data sources;
- Supports multiple Alternative-Keys via
Arrayfor decoding; - Supports Nested-Keys via
Stringwith dot syntax; - Supports customized encode/decode via subscripts;
- Supports builtin and custom Type-Conversions;
- Supports
struct,classand subclass; - Supports encode/decode with or without
IfPresent; - Supports abort (throws error) or continue (returns nil) encode/decode if error encountered;
- Uses JSON encoder/decoder by default, and supports PList;
- Uses Type-Inference, supports JSON
Data,StringandObject.
With Codable, it just needs to adop the Codable protocol without implementing any method of it.
struct TestAutoCodable: Codable, Equatable { private(set) var int: Int = 0 private(set) var string: String? enum CodingKeys: String, CodingKey { case int = "i", string = "s" } }
But, if you have to encode/decode manually for some reason, e.g. Alternative-Keys and Nested-Keys ...
struct TestManualCodable: Equatable { private(set) var int: Int = 0 private(set) var string: String? } extension TestManualCodable: Codable { enum Keys: CodingKey { case int, i case nested, string } init(from decoder: Decoder) throws { if let container = try? decoder.container(keyedBy: Keys.self) { if let int = (try? container.decodeIfPresent(Int.self, forKey: Keys.int)) ?? (try? container.decodeIfPresent(Int.self, forKey: Keys.i)) { self.int = int } if let nestedContainer = try? container.nestedContainer(keyedBy: Keys.self, forKey: Keys.nested), let string = try? nestedContainer.decodeIfPresent(String.self, forKey: Keys.string) { self.string = string } } } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: Keys.self) try? container.encodeIfPresent(int, forKey: Keys.int) var nestedContainer = container.nestedContainer(keyedBy: Keys.self, forKey: Keys.nested) try? nestedContainer.encodeIfPresent(string, forKey: Keys.string) } }
With ExCodable:
struct TestExCodable: Equatable { private(set) var int: Int = 0 private(set) var string: String? } extension TestExCodable: ExCodable { static let keyMapping: [KeyMap<Self>] = [ KeyMap(\.int, to: "int"), KeyMap(\.string, to: "string") ] init(from decoder: Decoder) throws { try decode(from: decoder, with: Self.keyMapping) } }
With ExCodable, it needs to to declare properties with var and provide default values.
struct TestStruct: Equatable { private(set) var int: Int = 0 private(set) var string: String? var bool: Bool! }
extension TestStruct: ExCodable { static let keyMapping: [KeyMap<Self>] = [ KeyMap(\.int, to: "int"), KeyMap(\.string, to: "string"), ] init(from decoder: Decoder) throws { try decode(from: decoder, with: Self.keyMapping) } // `encode` with default implementation can be omitted // func encode(to encoder: Encoder) throws { // try encode(to: encoder, with: Self.keyMapping) // } }
static let keyMapping: [KeyMap<Self>] = [ KeyMap(\.int, to: "int", "i"), KeyMap(\.string, to: "string", "str", "s") ]
static let keyMapping: [KeyMap<Self>] = [ KeyMap(\.int, to: "int"), KeyMap(\.string, to: "nested.string") ]
struct TestCustomEncodeDecode: Equatable { var int: Int = 0 var string: String? }
extension TestCustomEncodeDecode: ExCodable { private enum Keys: CodingKey { case int, string } private static let dddd = "dddd" private func string(for int: Int) -> String { switch int { case 100: return "Continue" case 200: return "OK" case 304: return "Not Modified" case 403: return "Forbidden" case 404: return "Not Found" case 418: return "I'm a teapot" case 500: return "Internal Server Error" case 200..<400: return "success" default: return "failure" } } static let keyMapping: [KeyMap<Self>] = [ KeyMap(\.int, to: Keys.int), ] init(from decoder: Decoder) throws { try decode(from: decoder, with: Self.keyMapping) string = decoder[Keys.string] if string == nil || string == Self.dddd { string = string(for: int) } } func encode(to encoder: Encoder) throws { try encode(to: encoder, with: Self.keyMapping) encoder[Keys.string] = Self.dddd } }
Using let to declare properties without default values.
struct TestSubscript: Equatable { let int: Int let string: String }
extension TestSubscript: Encodable, Decodable { enum Keys: CodingKey { case int, string } init(from decoder: Decoder) throws { // - seealso: // string = decoder.decode(<#T##codingKeys: CodingKey...##CodingKey#>) // string = try decoder.decodeThrows(<#T##codingKeys: CodingKey...##CodingKey#>) // string = try decoder.decodeNonnullThrows(<#T##codingKeys: CodingKey...##CodingKey#>) int = decoder[Keys.int] ?? 0 string = decoder[Keys.string] ?? "" } func encode(to encoder: Encoder) throws { // - seealso: // encoder.encode(<#T##value: Encodable?##Encodable?#>, for: <#T##CodingKey#>) // try encoder.encodeThrows(<#T##value: Encodable?##Encodable?#>, for: <#T##CodingKey#>) // try encoder.encodeNonnullThrows(<#T##value: Encodable##Encodable#>, for: <#T##CodingKey#>) encoder[Keys.int] = int encoder[Keys.string] = string } }
Declare struct FloatToBoolDecodingTypeConverter with protocol ExCodableDecodingTypeConverter and implement its method, decode values in alternative types and convert to target type:
struct FloatToBoolDecodingTypeConverter: ExCodableDecodingTypeConverter { public func decode<T: Decodable, K: CodingKey>(_ container: KeyedDecodingContainer<K>, codingKey: K, as type: T.Type) -> T? { // Bool -> Double if type is Double.Type || type is Double?.Type { if let bool = try? container.decodeIfPresent(Bool.self, forKey: codingKey) { return (bool ? 1.0 : 0.0) as? T } } // Bool -> Float else if type is Float.Type || type is Float?.Type { if let bool = try? container.decodeIfPresent(Bool.self, forKey: codingKey) { return (bool ? 1.0 : 0.0) as? T } } // Double or Float NOT found return nil } }
Register FloatToBoolDecodingTypeConverter with an instance:
register(FloatToBoolDecodingTypeConverter())
Cannot adopt ExCodable in extension of classes.
class TestClass: ExCodable, Equatable { var int: Int = 0 var string: String? = nil init(int: Int, string: String?) { (self.int, self.string) = (int, string) } static let keyMapping: [KeyMap<TestClass>] = [ KeyMap(ref: \.int, to: "int"), KeyMap(ref: \.string, to: "string") ] required init(from decoder: Decoder) throws { try decodeReference(from: decoder, with: Self.keyMapping) } static func == (lhs: TestClass, rhs: TestClass) -> Bool { return lhs.int == rhs.int && lhs.string == rhs.string } }
Requires declaring another static Key-Mapping for subclass.
class TestSubclass: TestClass { var bool: Bool = false required init(int: Int, string: String, bool: Bool) { self.bool = bool super.init(int: int, string: string) } static let keyMappingForTestSubclass: [KeyMap<TestSubclass>] = [ KeyMap(ref: \.bool, to: "bool") ] required init(from decoder: Decoder) throws { try super.init(from: decoder) try decodeReference(from: decoder, with: Self.keyMappingForTestSubclass) } override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) try encode(to: encoder, with: Self.keyMappingForTestSubclass) } static func == (lhs: TestSubclass, rhs: TestSubclass) -> Bool { return lhs.int == rhs.int && lhs.string == rhs.string && lhs.bool == rhs.bool } }
let test = TestStruct(int: 304, string: "Not Modified") let data = try? test.encoded() as Data? let copy1 = try? data?.decoded() as TestStruct? let copy2 = data.map { try? TestStruct.decoded(from: 0ドル) } XCTAssertEqual(copy1, test) XCTAssertEqual(copy2, test)
- iOS 8.0+ | tvOS 9.0+ | macOS X 10.10+ | watchOS 2.0+
- Xcode 12.0+
- Swift 5.0+
.package(url: "https://github.com/iwill/ExCodable", from: "0.5.0")
pod 'ExCodable', '~> 0.5.0'
- Code Snippets:
Title: ExCodable Summary: Adopte to ExCodable protocol Language: Swift
Platform: All
Completion: ExCodable
Availability: Top Level
<#extension/struct/class#> <#Type#>: ExCodable { static let <#keyMapping#>: [KeyMap<<#SelfType#>>] = [ KeyMap(\.<#property#>, to: <#"key"#>), <#...#> ] init(from decoder: Decoder) throws { try decode<#Reference#>(from: decoder, with: Self.<#keyMapping#>) } func encode(to encoder: Encoder) throws { try encode(to: encoder, with: Self.<#keyMapping#>) } }
- John Sundell (@JohnSundell) and the ideas from his Codextended
- ibireme (@ibireme) and the features from his YYModel
- Mr. Ming (@iwill) | i+ExCodable@iwill.im
ExCodable is released under the MIT license. See LICENSE for details.