Post

Codable

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 ✌️

This post is licensed under CC BY 4.0 by the author.