Introduction of Codable

The Codable protocol was introduced in Swift4.0 with the goal of replacing the existing NSCoding protocol with support for structures, enumerations, and classes. The introduction of Codable makes it easier to convert JSON and Swift types into strongly-typed data for use in code.

Codable is a combination of Encodable and Decodable protocols:

public typealias Codable = Decodable & Encodable
Copy the code
  • The Encodable protocol defines a method that:
public protocol Encodable {
    func encode(to encoder: Encoder) throws
} 
Copy the code

If the data complies with this protocol and all members in the data are encodable, the compiler automatically generates the relevant implementation. If it cannot be encoded, you need to customize it and implement it yourself.

  • The Decodable protocol defines an initialization function:
public protocol Decodable {
    init(from decoder: Decoder) throws
} 
Copy the code

Like Encodable, the compiler will automatically generate relevant implementations for you if all member attributes are Decodable.

The Swift standard library supports Codable protocols for types like String, Int, Double, and Data, Date, and URL by default. We don’t have to follow both Decodable and Encodable protocols, such as the Decodable protocol for a project that requires web data to be resolved to Swift. We need to follow Decodable and Encodable protocols on demand.


Decoding and coding process

After a brief introduction to Codable, let’s see how to use them first.

Struct Person: Codable {let name: String let age: Int} JSON = #" {"name":"Tom", "age": 2} "# let person = try JSONDecoder().decode(Person.self, from: json.data(using: .utf8)!) Print (person) // person (name: "Tom", age: 2) // JSONEncoder().encode(person) let dataObject = try? JSONSerialization.jsonObject(with: data0! , options: []) print(dataObject ?? "nil") //{ age = 2; name = Tom; } let data1 = try? JSONSerialization.data(withJSONObject: ["name": person.name, "age": person.age], options: []) print(String(data: data1! , encoding: .utf8)!) //{"name":"Tom","age":2}Copy the code

The above example decodes JSON data into Swift and encodes and exports JSON data. The member variables in Person are Codable, so the compiler automatically generates code for encoding and decoding. We only need to call decode(), encode() related functions.

So how does Codable work? When compiling code, an enumeration type definition of CodingKeys is automatically generated based on the properties of the type. This enumeration needs to contain the property fields that need to be encoded or decoded. The case name of the CodingKeys enumeration should match the name of the corresponding property in the type. The protocol is then automatically generated for init(from:) and encode(to:) for each type that is declared to implement the Codable protocol.

If the structure of Swift type is different from that of its encoding form, it can implement Encode and Decode protocol methods and define its own encoding and decoding logic.

struct Person: Codable {
    let name: String
    let age: Int
    var additionInfo: String?
    
    enum CodingKeys: String, CodingKey {
        case name, age
    }
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = try values.decode(String.self, forKey: .name)
        age = try values.decode(Int.self, forKey: .age)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        try container.encode(age, forKey: .age)
    }
}

Copy the code

JSON data parsing

Now that you know the encoding and decoding process, let’s look at some of the issues you need to pay attention to when using it in your project.

Field matching problem

Sometimes the back end interface returns data with different naming conventions than the front end. The back end may return the underscore name, but we usually use the camel name, so we need to change the field mapping. Use CodingKeys to specify an explicit mapping.

struct Person: Codable {
    let firstName: String
    let age: Int
    
    enum CodingKeys: String, CodingKey {
        case firstName = "first_name"
        case age
    }
}

// {"first_name":"Tom", "age": 2 }
Copy the code

After Swift4.1 JSONDecoder added the keyDecodingStrategy property, if the back end uses the underlined snake nomenclature, By setting the value of the keyDecodingStrategy attribute to convertFromSnakeCase, no additional code processing is required.

var decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
Copy the code

If only some of the attributes in the data type need to be parsed from JSON, or if there are many data fields in JSON and only some of them need to be parsed, you can override the CodingKeys enumeration to list only the fields that need to be parsed.

struct Person: Codable { let firstName: String let age: Int var additionalInfo: String? var addressInfo: String? enum CodingKeys: String, CodingKey {case firstName = "first_name" case age}} //JSON let JSON = #" {"first_name":"Tom", "age": 1, "additionalInfo":"123", "info":" ABC "} "# // decode let person = try JSONDecoder(). json.data(using: .utf8)!) print(person) //Person(firstName: "Tom", age: 2, additionalInfo: nil, addressInfo: nil)Copy the code

Nested types

Attributes in Swift data can be nested object types, arrays, or dictionary types. The entire data type is Codable if each element is Codable.

{" family_name ":" 101 ", "persons" : [{" name ":" xiao Ming ", "age" : 1}, {" name ":" little red ", "age" : 1}]}Copy the code
struct Person: Codable { let name: String let age: Int } struct Family: Codable { let familyName: String let persons: [Person] } let family = try JSONDecoder().decode(Family.self, from: json.data(using: .utf8)!) Print (family) // family (familyName: "101", persons: [__lldb_expr_59. 1), __lldb_expr_59.Person(name: "小红", age: 1)])Copy the code

Null value field problem

Sometimes the data returned by the back-end interface may have a value, or it may only return a null value. How to handle this?

{" familyName ":" 101 ", "person1" : {" name ":" xiao Ming ", "age" : 1}, "person2" : {}, "person3: null}Copy the code

The property needs to be set to optional and resolved to nil when returned as an empty object or null.

struct Person: Codable { var name: String? var age: Int? } struct Family: Codable { let familyName: String var person1: Person? var person2: Person? var person3: Person? } let family = try JSONDecoder().decode(Family.self, from: json.data(using: .utf8)!) Print (family) // family (familyName: "101", person1: Optional(__lldb_expr_83.Person(name: Optional(" xiao xiao ")), age: Optional(1))), person2: Optional(__lldb_expr_83.Person(name: nil, age: nil)), person3: nil)Copy the code

Init (from decoder: decoder) init(from decoder: decoder) init(from decoder: decoder);

init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        familyName = try container.decode(String.self, forKey: .familyName)
        //..
        person3 = try container.decodeIfPresent(Person.self, forKey: .person3) ?? Person(name: "defaultName", age: -1)
    }
Copy the code

The decodeIfPresent method is used above. When you are not sure if the key will exist, set the property to optional, and then use decodeIfPresent to find if the key exists, decode if it does, and return nil if it does not.

Enumerated values

In the data returned from the back end, some fields are identified in several categories and we want to convert them to enumerated types for easy use. For example, gender data:

{"name": "xiaoming ", "age": 1, "gender": "male"}Copy the code
enum Gender: String, Codable {
    case male
    case female
}
struct Person: Codable {
    var name: String?
    var age: Int?
    var gender: Gender?
}
Copy the code

Enumeration types need to be declared as primitive values for Codable protocols by default, and primitive values need to support Codable protocols. The above example uses a string as the primitive value of an enumeration type. The implicit primitive value of each enumeration member is the name of that enumeration member. If the corresponding data is an integer, enumeration can be declared as:

enum Gender: Int, Codable {
    case male = 1
    case female = 2
}
Copy the code

The date resolution

The JSONDecoder class declares a property of type DateDecodingStrategy that is used to define the parsing strategy for date types. Optional formats include secondsSince1970, zipecondssince1970, ISO8601, etc

let json = """
{
    "birthday": "2001-04-20T14:15:00-0000"
}
"""

struct Person: Codable {
    var birthday: Date?
}

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601

let person = try decoder.decode(Person.self, from: json.data(using: .utf8)!)
print(person)
//Person(birthday: Optional(2001-04-20 14:15:00 +0000))
Copy the code

JSONDecoder also provides customization by extending the DateFormatter to define a new format and passing this as a parameter to the dateDecodingStrategy.

extension DateFormatter {
    static let myDateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
        formatter.timeZone = TimeZone(secondsFromGMT: 8)
        formatter.locale = Locale(identifier: "zh_Hans_CN")
        return formatter
    }()
}

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(DateFormatter.myDateFormatter)
Copy the code

Plist file parsing

The Codable protocol doesn’t just support JSON data; it also supports PList files. It works in JSON format and doesn’t require any modifications to Codable protocols. Just replace JSONEncoder and JSONDecoder with the corresponding PropertyListEncoder and PropertyListDecoder.

Plist is essentially a special format standard XML document, so theoretically, we can refer to the Decoder/Encoder provided by the system to achieve their own arbitrary format of data serialization and deserialization scheme. Apple is also expected to extend the capabilities of other data formats by implementing new Decoder/Encoder classes. Codable doesn’t stop there; it has a lot of space to scale.


References

Developer.apple.com/documentati… Zhuanlan.zhihu.com/p/50043306 me. Harley – xk. Studio/posts / 20170…