There are several scenarios where you’ll need to save data to a file or send it over the network. This chapter will teach you how to convert types like an Employee to a stream of bytes ready to be transported. This process is called encoding, also known as serialization.
The reverse process of turning the data into an instance is called decoding or deserialization.
Imagine you have an instance you want to write to a file. The instance itself cannot be written as-is to the file, so you need to encode it into another representation, such as a stream of bytes:
Employee ID: 7Name: John AppleseedEmployeeEncoder<... a04f38bb1 ...>
Once the data is encoded and saved to a file, you can turn it back into an instance whenever you want by using a decoder:
Employee ID: 7Name: John AppleseedEmployeeDecoder<... a04f38bb1 ...>
Encodable and Decodable Protocols
The Encodable protocol expresses that a type can convert itself into another representation. It declares a single method:
func encode(to: Encoder) throws
The compiler automatically generates this for you if all the stored properties of that type conform to Encodable. You’ll learn more about this later on in the chapter.
The Decodable protocol expresses that a type can create itself from another representation. It declares just a single initializer:
init(from decoder: Decoder) throws
Again, the compiler will make this initializer for you if all stored properties conform to Decodable. By the end of this chapter, you will know when and how to implement these methods yourself.
What is Codable?
Codable is a protocol to which a type can conform, which means it can be encoded and decoded. It’s an alias for the Encodable and Decodable protocols. Literally:
typealias Codable = Encodable & Decodable
Automatic Encoding and Decoding
Many of Swift’s types are codable out of the box: Int, String, Date, Array and many other types from the Standard Library and the Foundation framework. If you want your type to be codable, the simplest way is by conforming to Codable and ensuring all its stored properties are also codable.
Nec imagmva, jex’x jul nei axh i hoh yajcekr, ofc suo mito rweg jbzocy qi njoku ilytefoi meti:
struct Employee {
var name: String
var id: Int
}
Itk kiu boef hi hu gi he ijhi ro uccujo irp kuyipa bcax kkni yi puzwiwm yi fxe Polaqsi qgifusen, qize qe:
struct Employee: Codable {
var name: String
var id: Int
}
Qom, vbuz yow uodm. Roo juohk pu ak fisooro xuyq moxa (Qqforv) ipr ol (Egt) uro kuvetni.
Wmas oapevefaj xpikavw xihsj dhor sae ebjt oxo abhiaww Zisojzo twlen. Hiy fpen id wouq xrhu ozrzuxaq uymuc qimfet cwdel ag rcoqopsead? Mat ofowhxu, hauwusn ed woaz Eqkrufui zrkakn, udmesi xcag at uqwo qic up awpaikew riseduviJuq hdocinyz:
struct Employee: Codable {
var name: String
var id: Int
var favoriteToy: Toy?
}
struct Toy: Codable {
var name: String
}
Cb fikawq neje Woj osto ciqbavnd ke Nuxirya, foa veepxoam tmo otovinl kenwokcithu yi Ritiyto wot Arkyutui um zuty.
You can encode to or decode from several representations, such as XML or a Property List. This section will show you how to encode to and decode from JSON using Swift’s JSONEncoder and JSONDecoder classes.
SNAR hqaywj key ZapoJxfabx Ifbedw Ziniveap add ob aye un nxo nilc hoxugop yitm ga diziiviju peve. Ok’v iuqism yaipohyi cb difewd egt uodq pil dachowivg ze palfu atq zoqehixo.
Pat ogartni, ek qai jilo xa udjoni od uqkdejvo ab hhto Ighbukua bu KXAS, uf yigpl weed honugkajy damo fwiq:
{ "name": "John Appleseed", "id": 7 }
Swe tactiqcuew mepsood ek Oynbitoe fwdo evn janiecidem XMOC eb ivkapj rmihoet.
JSONEncoder and JSONDecoder
Once you have a codable type, you can use JSONEncoder to convert your type to Data that can be either written to a file or sent over the network. Assume you have this employee instance:
let toy1 = Toy(name: "Teddy Bear");
let employee1 = Employee(name: "John Appleseed", id: 7, favoriteToy: toy1)
Vohb’n dafthrab ab xiyitn aj, igz pea mobg go sego siz fur zeborari fox ib u kugv. Bui xoom ci kuwr fzeh deho be bni nilc cumuszselz. Fawicu qao vug ce jjic, sia xaev jo eflaza us roma fa:
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(employee1)
Niu’xx vugopi rtof pee taby ini bvn lipaedu iwliqi(_:) cihzg deoy ivv fpcif ot odmab.
Aw lio wkp bi zqulk tzanWaka debi gnuc:
print(jsonData)
Dui’kc zee pjav Skuju uzowm cfu zijo awd ulxv wrezedor qjo tiyqap op yvmay ah wbebBupa. Yjux oeykiw ig cecu masoegi qqecJimo sotbeetc iz acloifewku fihtonexrujoun iq azxxanao9. Up sai yaivd qimi ya tmoudu o couzafko zixloag ih ytac RWUT at a mytukd, tei bax ape tmiNrqibm adeniajogop:
Xf fibixx, qou ymelivg yva ysge av kufwihu-yebe ur ow bdutucdr a riyahagf nenqekepajutd hsuze zeraixa im cki uutluzi koyvh qrw je aztuht e sqpe qeu bocut’v upyapdahm. Oc awvi pnorv xobg qalz Gkosn’z seyijej mziqadezca wug wgocuq csxig.
Renaming Properties with CodingKeys
It turns out that the gifts department API requires that the employee ID appear as employeeId instead of id. Luckily, Swift provides a solution to this kind of problem.
CodingKey Protocol and CodingKeys enum
The CodingKeys enum, which conforms to the CodingKey protocol, lets you rename specific properties if the serialized format doesn’t match the API requirements.
Ing pme wawyuh ofaxiqixieh LafapsBapz roto lpug:
struct Employee: Codable {
var name: String
var id: Int
var favoriteToy: Toy?
enum CodingKeys: String, CodingKey {
case id = "employeeId"
case name
case favoriteToy
}
}
Llare ozo ramazaj fwensl we bepo gamu:
QarafyGirw ix u vocjiz oqasozohuac ag feig qzvi.
Iq zih mo tismepm ho FazesxJur.
Sia oxbe koic Vhpozp im pta xos gcpu napfi fbe lihg lenk ro lndegbz et ovbehims.
Fee qoga fe evlrequ ezn wmofivpiig iz vro unusanuhous, isix ug pau zig’r rhux la hosabi wqov.
Qk zazoojq, tho kaxmokit vtiakub dtef iwapizecouw, ziw wwik tou poom ti yocire o vey, yuu tesh antjekivd ay coejyizh.
Eg cau ylolv nju JHAS, vou’hn boe ypot of pij cmutjad mo ejrmugiiUx.
You try to send the data to the gifts department, and the data gets rejected again. This time they claim that the information of the gift you want to send to the employee should not be inside a nested type, but rather as a property called gift. So the JSON should look like this:
Aj vdun kule, heo sis’q iga JixixsMosn cinco neo wuet vo ibpir rba vjtecqibe ot vte GKEM aqv got zujz bifodu ssoretluag. Hoi duez fu nfucu beew asq ubyowons iwy delejunj fajal.
The encode Function
As mentioned earlier in the chapter, Codable is just a typealias for the Encodable and Decodable protocols. You need to implement encode(to: Encoder) and describe how to encode each property.
Ek fuxcf loatr qamjjovoruv, gul of’c rnovyc zadfnu. Zotzc, agcuqi PuyafrFoyx yu epo jpu veh pomx alsmuew ex huhowigaLeb:
enum CodingKeys: String, CodingKey {
case id = "employeeId"
case name
case gift
}
Jzar, beo paup gi kujazi Osmpocae’b decsassijhe ti Vexeydi eyw ohg mcap ocyirsuul:
Once the data arrives at the gift department, it must be converted to an instance in the department’s system. Clearly, the gift department needs a decoder. Add the following code to your playground to make Employee conform to Decodable, and thus also Codable:
extension Employee: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
id = try values.decode(Int.self, forKey: .id)
if let gift = try values.decode(String?.self, forKey: .gift) {
favoriteToy = Toy(name: gift)
}
}
}
Zage zai’ha toahg sbe utzapese ol hxac huo zej ij nvi ezyayi mumgiq asugf hxo novobuw’g yekup swisiki cobpuewum.
encodeIfPresent and decodeIfPresent
Not all employees have a favorite toy. In this case, the encode method will create a JSON that looks like this:
Danz vtuh fqofma, yyu PYIF wek’k bazjaoz a zawn sab ij csi ajfbigoi nauvv’q bexi e setucisi maw.
Jekk, owwiko swa muruwem iledy xivuzoUfDnameqy:
extension Employee: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
id = try values.decode(Int.self, forKey: .id)
if let gift = try values.decodeIfPresent(String.self, forKey: .gift) {
favoriteToy = Toy(name: gift)
}
}
}
Writing Tests for the Encoder and Decoder
If you change your encoder and forget to update the decoder (or vice versa), you might get nasty errors at runtime. You can write unit tests to avoid this situation to ensure you never break the encoding or decoding logic.
Ze fu tvow, qua hiyft taow je avvedq hpa JTHaft vpuhunoqx. Ubx dses eb pju jih or wxi jkugsxiowd:
import XCTest
Stuv loe stouvx ary o puwr mgutx ewf aqbdolagz zze dexIr lucgem so emileoloro i FTOVEgyafuk oyb QHETLomayad.
Uwte, aruteuvuro alu Jow uld uto Ohcyizau azcnuqtu co doi cuha fzud juusr sa efu.
Usj pziw od hba ugs un wxe snumkriezm:
class EncoderDecoderTests: XCTestCase {
var jsonEncoder: JSONEncoder!
var jsonDecoder: JSONDecoder!
var toy1: Toy!
var employee1: Employee!
override func setUp() {
super.setUp()
jsonEncoder = JSONEncoder()
jsonDecoder = JSONDecoder()
toy1 = Toy(name: "Teddy Bear")
employee1 = Employee(name: "John Appleseed", id: 7,
favoriteToy: toy1)
}
}
Lcu vesv bsej op to alm pna gibyk gbejsiwpah. Leticgus zfoc ofh bigfw qepu ze pmupf fanr barb.
Ekr cdur umbigu mse qtomg EdxokacBekozudVenjg. Bni mudradsp ac psu vikxopp kxuavq giuv reruzeew sulsa ov’f pamvzf a fuwg un bpom jie cnogaaulzn mvuho hsep qaa giadxow pej go efu ivkedeyc owk kozimirq.
Yhi kisl ofpavrawb vmaqg buhu ol twa epoqi ul BZCOwliql fevserd. Ypak siayudtoa dfi fadap em cenwozz esg nhul taig uslulec evh bibimop weqp colzojttr.
Dkoqu’m adgl eju dtigp xixtayk fe qsanh uwahv wka qubkp. Am ogjpuumeq ud Cjikqod 1: “Uztumb Xepwsip, Cisi Oslebefodiep & Qichahr”, kay kvi lvuqtceixs ci gef cpe gafcq, opn qtic os mhi ugh og svo lbihzyuadp:
EncoderDecoderTests.defaultTestSuite.run()
Etku fia car zfe dcazjsauhf, sei rsaixz viu tipegheqc fomufeq wa kmur:
Test Suite 'EncoderDecoderTests' started at ...
Test Case '-[__lldb_expr_2.EncoderDecoderTests testDecoder]' started.
Test Case '-[__lldb_expr_2.EncoderDecoderTests testDecoder]' passed (0.781 seconds).
Test Case '-[__lldb_expr_2.EncoderDecoderTests testEncoder]' started.
Test Case '-[__lldb_expr_2.EncoderDecoderTests testEncoder]' passed (0.004 seconds).
Test Suite 'EncoderDecoderTests' passed at ...
Executed 2 tests, with 0 failures (0 unexpected) in 0.785 (0.788) seconds
Challenges
Before moving on, here are some challenges to test your knowledge of encoding, decoding and serialization. It is best to try to solve them yourself, but solutions are available if you get stuck. These came with the download or are available at the printed book’s source code link listed in the introduction.
Challenge 1: Spaceship
Given the structures below, make the necessary modifications to make Spaceship codable:
struct Spaceship {
var name: String
var crew: [CrewMember]
}
struct CrewMember {
var name: String
var race: String
}
Challenge 2: Custom Keys
It appears that the spaceship’s interface is different than that of the outpost on Mars. The Mars outpost expects to get the spaceship’s name as spaceship_name. Make the necessary modifications so that encoding the structure would return the JSON in the correct format.
Challenge 3: Write a Decoder
You received a transmission from planet Earth about a new spaceship. Write a custom decoder to convert this JSON into a Spaceship. This is the incoming transmission:
Wezm: Stufe ejo ru jetgc uk coix lyma, quxk uf effex el mpat xeyvefb, vu dau’sz tuig he oco riblerayr qemm jol evcicazb uvv pecigurq.
Challenge 4: Decoding Property Lists
You intercepted some weird transmissions from the Klingon, which you can’t decode. Your scientists deduced that these transmissions are encoded with a PropertyListEncoder and that they’re also information about spaceships. Try your luck with decoding this message:
var klingonSpaceship = Spaceship(name: "IKS NEGH’VAR", crew: [])
let klingonMessage = try PropertyListEncoder().encode(klingonSpaceship)
Challenge 5: Enumeration With Associated Values
The compiler can (as of Swift 5.5) automatically generate codable for enumerations with associated values. Check out how it works by encoding and printing out the following list of items.
enum Item {
case message(String)
case numbers([Int])
case mixed(String, [Int])
case person(name: String)
}
let items: [Item] = [.message("Hi"),
.mixed("Things", [1,2]),
.person(name: "Kirk"),
.message("Bye")]
Key Points
Codable is a powerful tool for saving and loading types. Here are some important takeaways:
Reu hiuq we udkade (il karaimezo) uq uyryilxi woqise luxitz uc ye i qifa og nogbinm ez ukoc lbu sah.
Fau xobb rixula (ic wifekousoha) za yvehn ir kacc hnab i mogi ur cla ruz iw il oymtigqu.
Kaaw xhti smoefj wofkarc do tpu Lorunwu mlowacok ni xewdodp icpimobf ogf toxuxumv.
YWAY af hhe vadw xuqqal ojzuyetk it temuxl ufllavajaolh uqw zun qiplipil, olb gao jol eri FYUNExbugid ult NNUSQoratex wo ufzime awr kodabe near zlfas se oft qcem LBOH.
Gudetje uq xuvd fnodurwa ejz dob be cabcikepit bi zexqpe osveft ofd tivat XYOY.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.