For the More Curious: Manually Conforming to Codable

In this chapter, you took advantage of automatic conformance to the Codable protocol and did not need to implement encode(to:) or init(from:) yourself. What if you had some property that was not codable? Let’s take a look at how to implement those two methods.

The code you add in this section will conflict with later chapters, so create a copy of your LootLogger project to work in, as you do for challenges. Open Item.swift. Create a new enumeration to describe the category of an item and add a property to reference the category.

Listing 13.11  Adding a Category enumeration (Item.swift)

enum Category {
    case electronics
    case clothing
    case book
    case other
}

var category = Category.other

(For brevity, the category property is given a default value. In practice, you would probably want to add an additional parameter to the initializer to allow the category to be customized.)

With the introduction of the category property, Codable conformance is no longer automatic. The first thing you need to do to regain codable functionality is to define the keys that will be used during encoding. You can think of these as being like the keys to a dictionary.

In Item.swift, define another enumeration named CodingKeys that conforms to the CodingKey protocol.

Listing 13.12  Adding a CodingKeys enumeration (Item.swift)

enum CodingKeys: String, CodingKey {
    case name
    case valueInDollars
    case serialNumber
    case dateCreated
    case category
}

The CodingKeys enumeration has a raw value of type String, as indicated in its declaration. This means that every case is associated with a string of the same name as the case. (You will learn about enumerations with raw values in Chapter 20.)

It also conforms to the CodingKey protocol. The CodingKey protocol effectively has one requirement: a stringValue for each of the keys. Since the CodingKeys enum is backed by a String raw value, this requirement is automatically satisfied.

With the CodingKeys enumeration created, you can now implement encode(to:).

Listing 13.13  Implementing encode(to:) (Item.swift)

func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)

    try container.encode(name, forKey: .name)
    try container.encode(valueInDollars, forKey: .valueInDollars)
    try container.encode(serialNumber, forKey: .serialNumber)
    try container.encode(dateCreated, forKey: .dateCreated)

    switch category {
    case .electronics:
        try container.encode("electronics", forKey: .category)
    case .clothing:
        try container.encode("clothing", forKey: .category)
    case .book:
        try container.encode("book", forKey: .category)
    case .other:
        try container.encode("other", forKey: .category)
    }
}

First, you create a container. Generally, you will want a keyed container that acts like a dictionary. You specify which keys the container supports by passing it the CodingKeys enumeration.

After the container is created, you encode each piece of data that you want to persist. The encode(_:forKey:) method can fail if the value passed in is invalid for the current context, so it must be annotated with try.

Encoding the original Item properties is pretty straightforward. For the new category property, you cannot just encode the property itself. After all, that is the issue you are addressing here: Category is not Codable itself, so you must convert it to a type that is. To do this, you switch over the category and encode a string for each case.

With the encode(to:) method implemented, let’s implement init(from:).

Listing 13.14  Implementing init(from:) (Item.swift)

required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)

    name = try container.decode(String.self, forKey: .name)
    valueInDollars = try container.decode(Int.self, forKey: .valueInDollars)
    serialNumber = try container.decode(String?.self, forKey: .serialNumber)
    dateCreated = try container.decode(Date.self, forKey: .dateCreated)

    let categoryString = try container.decode(String.self, forKey: .category)
    switch categoryString {
    case "electronics":
        category = .electronics
    case "clothing":
        category = .clothing
    case "book":
        category = .book
    case "other":
        category = .other
    default:
        category = .other
    }
}

This initializer’s role is to pull out the data you need from a container. Once again, you specify which keys are associated with the container. Then you decode the properties, specifying the type and key for each. For the category property, you need to decode the string that was encoded earlier, check the contents of that string, and then assign the corresponding enumeration case.

Build the application and notice that the errors are addressed. You have now restored codable functionality to Item.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.156.140