En | 中文
- Uses
@propertyWrapperinstead of key-mapping, the new syntax is more concise, elegant and easier to use. - Optimized type conversions.
- Fix bugs.
struct TestExCodable: ExAutoCodable { @ExCodable private(set) var int: Int = 0 @ExCodable("nested.nested.string", "string", "str", "s") private(set) var string: String? = nil }
- Converts between JSON and models.
- Based on and Extends Swift
Codable. - Associates JSON keys to properties via annotations -
@propertyWrapper:ExCodabledid not read/write memory via unsafe pointers.- No need to encode and decode properties one by one.
- In most cases, the
CodingKeytypes are no longer necessary,Stringliterals are preferred. - Currently, requires declaring properties with
varand provide default values.
- Supports alternative keys for decoding.
- Supports nested keys via
Stringwith dot syntax. - Supports builtin and custom type conversions, including nested optionals as well.
- Supports manual encoding/decoding using subscripts.
- Supports
return nilorthrow errorwhen encoding/decoding failed. - Supports
struct,enum,classand subclasses. - Supports JSON
Data,String,ArrayandDictionary. - Supports type inference.
- Uses JSON encoder/decoder by default, supports PList and custom encoder/decoder.
- Super lightweight.
TODO:
- Supports
let. - Supports
varwithout default values. - Use Macros instead of protocol
ExAutoCodable.
ExCodable requires declaring properties with var and provide default values.
struct TestStruct: ExAutoCodable { @ExCodable private(set) var int: Int = 0 @ExCodable("string") private(set) var string: String? = nil }
struct TestAlternativeKeys: ExAutoCodable { @ExCodable("string", "str", "s") private(set) var string: String! = nil }
struct TestNestedKeys: ExAutoCodable { @ExCodable("nested.nested.string") private(set) var string: String! = nil }
enum TestEnum: Int, Codable { case zero = 0, one = 1 } struct TestStructWithEnum: ExAutoCodable { @ExCodable("enum") private(set) var `enum` = TestEnum.zero }
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 }
ExCodable builtin type conversions:
- boolean:
BoolInt,Int8,Int16,Int32,Int64UInt,UInt8,UInt16,UInt32,UInt64String
- integer:
BoolInt,Int8,Int16,Int32,Int64UInt,UInt8,UInt16,UInt32,UInt64Double,FloatString
- float:
Int,Int8,Int16,Int32,Int64UInt,UInt8,UInt16,UInt32,UInt64Double,FloatString
- string:
BoolInt,Int8,Int16,Int32,Int64UInt,UInt8,UInt16,UInt32,UInt64Double,FloatString
Custom type conversions for specific properties:
struct TestCustomEncodeDecode: ExAutoCodable { @ExCodable(decode: { decoder in if let string: String = decoder["string"] { return string.count } return 0 }) private(set) var int: Int = 0 }
Custom type conversions for specific 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 } }
Custom type conversions for global:
struct TestCustomGlobalTypeConverter: ExAutoCodable, Equatable { @ExCodable("boolFromDouble") 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 } }
A type without ExCodable:
struct TestManualEncodingDecoding { let int: Int let string: String }
Manual encoding/decoding using subscripts:
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 } }
While encoding/decoding, ExCodable ignores the keyNotFound, valueNotFound, invalidValue and typeMismatch errors and return nil by default, only throws JSON errors.
ExCodable also supports throw errors:
- Use
nonnull: trueto throwEncodingError.invalidValue,DecodingError.keyNotFound,DecodingError.valueNotFound. - Use
throws: trueto throwDecodingError.typeMismatch.
struct TestNonnullAndThrows: ExAutoCodable { @ExCodable(nonnull: true, throws: true) private(set) var int: Int! = 0 }
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) } }
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
See the tests for more examples.
- iOS 12.0+ | tvOS 12.0+ | macOS 11.0+ | watchOS 4.0+
- Xcode 15.4+
- Swift 5.10+
Swift Package Manager:
.package(url: "https://github.com/ExCodable/ExCodable", from: "1.0.0")
CocoaPods:
pod 'ExCodable', '~> 1.0.0'
When you update to ExCodable 1.0.
Step 1: Update your code to use the old API - DEPRECATED but quick.
- Replace protocol
ExCodablewithExCodableDEPRECATED. - Add
staticto funcdecodeForTypeConversion(_:codingKey:as:)of protocolKeyedDecodingContainerCustomTypeConversion.
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) } }
Step 2: Upgrade your models to the new API one by one - SUGGESTED:
- Replace
protocolExCodablewithExAutoCodable. - Remove initializer
init(from decoder: Decoder) throws. - Remove
staticpropertieskeyMapping. - Use
@ExCodable("<key>", "<alt-key>", ...). - See Usage for more details.
struct TestExCodable: ExAutoCodable { @ExCodable private(set) var int: Int = 0 @ExCodable("nested.nested.string", "string", "str", "s") private(set) var string: String? }
Hope ExCodable will help you! Make a star ⭐️ 🤩
- John Sundell (@JohnSundell) and the ideas from his Codextended
- ibireme (@ibireme) and the features from his YYModel
- Míng (@iwill) | [email protected]
ExCodable is released under the MIT license. See LICENSE for details.