En | 中文
- Uses
@propertyWrapper
instead 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
:ExCodable
did not read/write memory via unsafe pointers.- No need to encode and decode properties one by one.
- In most cases, the
CodingKey
types are no longer necessary,String
literals are preferred. - Currently, requires declaring properties with
var
and provide default values.
- Supports alternative keys for decoding.
- Supports nested keys via
String
with dot syntax. - Supports builtin and custom type conversions, including nested optionals as well.
- Supports manual encoding/decoding using subscripts.
- Supports
return nil
orthrow error
when encoding/decoding failed. - Supports
struct
,enum
,class
and subclasses. - Supports JSON
Data
,String
,Array
andDictionary
. - Supports type inference.
- Uses JSON encoder/decoder by default, supports PList and custom encoder/decoder.
- Super lightweight.
TODO:
- Supports
let
. - Supports
var
without 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:
Bool
Int
,Int8
,Int16
,Int32
,Int64
UInt
,UInt8
,UInt16
,UInt32
,UInt64
String
- integer:
Bool
Int
,Int8
,Int16
,Int32
,Int64
UInt
,UInt8
,UInt16
,UInt32
,UInt64
Double
,Float
String
- float:
Int
,Int8
,Int16
,Int32
,Int64
UInt
,UInt8
,UInt16
,UInt32
,UInt64
Double
,Float
String
- string:
Bool
Int
,Int8
,Int16
,Int32
,Int64
UInt
,UInt8
,UInt16
,UInt32
,UInt64
Double
,Float
String
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: true
to throwEncodingError.invalidValue
,DecodingError.keyNotFound
,DecodingError.valueNotFound
. - Use
throws: true
to 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
ExCodable
withExCodableDEPRECATED
. - Add
static
to 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
protocol
ExCodable
withExAutoCodable
. - Remove initializer
init(from decoder: Decoder) throws
. - Remove
static
propertieskeyMapping
. - 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.