From Apple doc
Codable is a type alias for the Encodable and Decodable protocols. When you use Codable as a type or a generic constraint, it matches any type that conforms to both protocols.
1
| public typealias Codable = Decodable & Encodable
|
Basic Usage
1
2
3
4
5
6
| // Codable Object:
struct Employee: Codable {
let name: String
let age: Int
let designation: String
}
|
Make Network Request to Decode JSON to Codable Model
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| typealias RequestCompletionHandler<T: Decodable> = (_ value: T?, _ error: Error?) -> Void
func callAPI<T: Decodable>(completionHandler: RequestCompletionHandler<T>) {
let json = #" {"name":"John", "age":30, "designation":"software developer"} "#.data(using: .utf8)!
do {
let value = try JSONDecoder().decode(T.self, from: json)
completionHandler(value, nil)
} catch {
completionHandler(nil, error)
}
}
callAPI { (model: Employee?, error) in
if let demoModel = model {
print("\(demoModel)") //Employee(name: "John", age: 30, designation: "software developer")
} else if let error = error {
print("Error: \(error)")
}
}
|
Key Mapping
- You can omit
CodingKeys
if the related properties of the Codable model and keys are identical.
1
2
3
4
5
6
7
| struct Employee: Codable {
let birthYear: String
enum CodingKeys: String, CodingKey {
case birthYear = "birth_year"
}
}
|
- There is no need to add additional code to handle the mapping if the backend utilises the snakecase nomenclature by setting
keyDecodingStrategy
.
1
2
| let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase // birth_year to birthYear
|
Parse null value
- Add
init(from decoder: Decoder) throws
method to specify a default value.
1
2
3
4
5
6
7
8
| struct Employee: Codable {
let birthYear: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.birthYear = try container.decodeIfPresent(String.self, forKey: .birthYear) ?? "1900"
}
}
|
- An optional type for the property can be specified.
1
2
3
| struct Employee: Codable {
let birthYear: String?
}
|
- Generic
@propertyWrapper
can be used to provide default values for non-optional properties when they are absent or have a nil value.
Original Source: https://gist.github.com/lnfnunes/f9af5d188b806f9f2538ef43ebe8b70c
Helper:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
| enum DecodableDefault {}
protocol DecodableDefaultSource {
associatedtype Value: Decodable
static var defaultValue: Value { get }
}
extension DecodableDefault {
@propertyWrapper
struct Wrapper<Source: DecodableDefaultSource> {
typealias Value = Source.Value
var wrappedValue = Source.defaultValue
}
typealias Source = DecodableDefaultSource
typealias List = Decodable & ExpressibleByArrayLiteral
typealias Map = Decodable & ExpressibleByDictionaryLiteral
// Convenience aliases to reference the provided `enum Sources` as specialized versions of our property wrapper type.
typealias True = Wrapper<Sources.True>
typealias False = Wrapper<Sources.False>
typealias EmptyString = Wrapper<Sources.EmptyString>
typealias EmptyList<T: List> = Wrapper<Sources.EmptyList<T>>
typealias EmptyMap<T: Map> = Wrapper<Sources.EmptyMap<T>>
typealias Zero = Wrapper<Sources.Zero>
typealias One = Wrapper<Sources.One>
enum Sources {
enum True: Source {
static var defaultValue: Bool { true }
}
enum False: Source {
static var defaultValue: Bool { false }
}
enum EmptyString: Source {
static var defaultValue: String { "" }
}
enum EmptyList<T: List>: Source {
static var defaultValue: T { [] }
}
enum EmptyMap<T: Map>: Source {
static var defaultValue: T { [:] }
}
enum Zero: Source {
static var defaultValue: Int { 0 }
}
enum One: Source {
static var defaultValue: Int { 1 }
}
}
}
extension DecodableDefault.Wrapper: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(Value.self)
}
}
extension DecodableDefault.Wrapper: Equatable where Value: Equatable {}
extension DecodableDefault.Wrapper: Hashable where Value: Hashable {}
extension DecodableDefault.Wrapper: Encodable where Value: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
}
extension KeyedDecodingContainer {
func decode<T>(_ type: DecodableDefault.Wrapper<T>.Type, forKey key: Key) throws -> DecodableDefault.Wrapper<T> {
try decodeIfPresent(type, forKey: key) ?? .init()
}
}
|
Usage:
1
2
3
| struct Employee: Codable {
@DecodableDefault.EmptyString var birthYear: String
}
|
Make enum
Codable
Only the value that fits the case in the enum will be processed; otherwise, you will get a DecodingError
.
1
2
3
4
5
6
7
8
9
| enum Job: String, Codable {
case developer
case designer
case management
}
struct Employee: Codable {
var designation: Job
}
|
Conforming an Enum to CodableEnum
and then implementing the defaultValue
will allow you to provide default value a when you come across an unknown case.
Helper:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| protocol CodableEnum: Codable, RawRepresentable {
static var defaultValue: Self { get }
}
extension CodableEnum where Self.RawValue == Int {
init(from decoder: Decoder) throws {
self = try Self(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? Self.defaultValue
}
}
extension CodableEnum where Self.RawValue == String {
init(from decoder: Decoder) throws {
self = try Self(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? Self.defaultValue
}
}
|
Usage:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| enum Job: String, CodableEnum {
static var defaultValue: Job {
.none
}
case developer
case designer
case management
case none
}
struct Employee: Codable {
var designation: Job
}
|
Happy Coding ✌️