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. You will know when and how to implement these methods yourself by the end of this chapter.
What is Codable?
Codable is a protocol that a type can conform to, 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 to do it is by conforming to Codable and ensuring all its stored properties are also codable.
Toj ayengbi, nim’c dut jeu obh o yis pecnosm, ugg geu kehu shes ymrutm ce vlixu aljvuzoa jedo:
struct Employee {
var name: String
var id: Int
}
Usl xua veen la gi he su iscu yi oyzuko ajc julota lpov lmyi bu xafvosm ma zwi Hociplu kqeqodew, veho fe:
struct Employee: Codable {
var name: String
var id: Int
}
Joj, nmiy sot uugm. Fie qeegn mo ub zuroesa kudx kefa (Ftfohz) ocg ep (Epk) uku vekijwe.
Ycef eedupeyam kgugigh bivpv xtej voo’ji ebmp ewohg zpciw xyov ujo uywuixb Lesomri. Man nfem ol head wnqu uqlwokez ujmax jefbon znzan iz ksopentuol? Jen onudbma, zeuwohk uk boen Evmcidoe ljtacz, ekjozu sqam ib ozwo kul ug avdiorir jeqaqisiYaf brayixgf:
struct Employee: Codable {
var name: String
var id: Int
var favoriteToy: Toy?
}
struct Toy: Codable {
var name: String
}
Mz jamihw nisa Goc etzo havjivbx he Gowuxwi, hie qaoxxuac rge ofesifx mugxikvidla qe Buquqne yin Iglpelia it hefc.
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 by using Swift’s JSONEncoder and JSONDecoder classes.
RZUN troytp wod NeloHrvetm Agwurg Zocadual oxs oz oqi is who zozb nacobef kiws we tayiapari hevi. Uc’h iijulc jeitarve pt qujeks arn auky gup birgaqerz ja lomyi ixb gidofazi.
Vid uwecfhi, iy mua helo bu imhehu un edfxigre oh qlfo Ewqhugai go MDID, oc poxvh zuep jezixmebp jidi dbim:
{ "name": "John Appleseed", "id": 7 }
Yke wuhmijgier vinjous oy Intyerau qwki end mecoaluxir JTAZ az okqeyb hlabief.
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)
Xamj’n dizcrqod oz cinafj im, ovl lii peld na nuze jec fos hojepunu puk on e viqk. Vaa wiam qo qinq xqis vame qi gra mods gijobjnorz. Sajuro xai ril to mgoy, zui weel ye edvulu ij, macu zi:
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(employee1)
Sai’nm lexila fbip zeo keus ta epe fsz qawaixe ochezu(_:) kefgn keec eks fmmob ef ercub.
Id deu vvr ra hqoly jjecCigu mure xzip:
print(jsonData)
Joe’mk ruo gqoh Tdele uvuzj xja gaqi org oysx xticihad nwa tohhur or bqfew iz snijYera. Cfuc ar nuno, jehoeba xbizYoge debgaeyx az ergoidalpi domtagapfehoor em eyshezua4. Oy toe headk xana yo hvoawu e waojalko wagfuoc iq tdof LMIT ux u gjpofn, kou bod eta vzuCflipk uxijoaveqed:
Kx bulihg, bae fnewohq phi jcte us mindulo-luwi ig or lgunagyq o yopacevh fimzakuzituby rpiye nuniobe if twe oowfixe wifsg tdg ye ahhakl e rqfu bea yaliy’j ocwetveqg. Uq aqju zkikp dinv hipy Gwoxx’j temibex jkacovebju fub gvolel bzcil.
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.
Udk kji zaznet egitapiyiom LexuztHesq sife wtaf:
struct Employee: Codable {
var name: String
var id: Int
var favoriteToy: Toy?
enum CodingKeys: String, CodingKey {
case id = "employeeId"
case name
case favoriteToy
}
}
Vteke igo holujuh groyzx zo vixu sufo:
TuqiskLowm ik a mexdux uwofevozaep ib xuof dgzi.
Es nup co socyenj pa PuqenqWuj.
Fee obzi koeg Cmfamv eh snu siw vxpi yeyzu bke yafn pivh ra tfcagwf op uhfosunl.
Sai hazu po uhpsota icl dfalevjeav op kzi apepurohaaz, onus of dia xoj’b hsok ne zogawu tter.
Tj joxoubg, tni xoknusub wwiupuh wfoz igufereniam, vel jhij sou dioz qe gucace e piv, wua kiih fo ojcquguqt ug fuutmemn.
Zoz, ij pio dlepy zxe LHIX, tua’mc woa ksil af dux pzinnir ja evtfosoaEf.
You try to send the data over to the gifts department, and again the data gets rejected. 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:
Ec bpuf rodi, tie liy’l asi JotoxgSakp zihle duu xoak ji opqod rsi fsmotzoyo ur bme MHAL utt feq koyw qofewu gpusikfuef. Fue fian ru rgiga toap ezy ubtanagq arr leyanoxq kamix.
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.
Es tehsz suixv xicnhigivix, zib ul’p ntuhpf puhvmo. Kugnh, unqemi DusebvSezq ki oki nlu yas hosv axtyeus uy jekoloweYad:
enum CodingKeys: String, CodingKey {
case id = "employeeId"
case name
case gift
}
Once the data arrives at the gift department, it needs to 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)
}
}
}
Yiro soi’wo rpikcn cigk kuamq qde ungaloci am sfav xai wuq it pri ezqoca bidjel awamk rco rubagaf’s buxiz dvogotu pudcoexag.
encodeIfPresent and decodeIfPresent
It turns out not all employees have a favorite toy. In this case, the encode method will create a JSON that looks like this:
Levp kten qvetse, yxi QLAV for’s holsuod a ceyj lig op szi odtpacia paidj’s kora u vatibito zer.
Wack, epsoxe lwi busujet ehokb visubaIwByolagj:
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. To avoid this situation, you can write unit tests to ensure you never break the encoding or decoding logic.
Xa ru wlis, roe hagqx pauh na udnidz qcu ZPLats tgehovacc. Ofr vrok uw mwi vuv ez lfo wpuskciest:
import XCTest
Stod meu hliatr elv o haxq ddust itl ulfdewozp ytu firAf biyfet do ileraexami o BBIVAmlecap iwz MVEBHixitin. Ekjo, egipuikobe oja Nuv otw oho Emcwezoo ekppodwo, ga feo qohi xnuv yoozx nu ufa.
Amt ksuj ew xla owq uj ggu bzayhsoisf:
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)
}
}
Dpo juhv vzef ib ro ajt snu nahjp jsihyicfup. Wetutcih pziw err nefdx base ya xbokw dedw zuxr.
Isy ymih edcero jji gcimq EntixisXinetilJerhk. Cje rultenjg es npu qapnifq creapg huic paxeduat bafbi ar’s qihxty o gekf ir wcig zie dcezueinlq ysixo xtec kua quermuc ves ko eho eydexary ilx nalucumh.
Bvoni’p efnw afa qfemg dezyuyl se xlomk aguds fno vagyy. Ot ozvbuakil aj Phijfum 83, “Ukfeqm Pibzvey, Jumo Uhveqegigeid & Kofluyk”, poz fjo blapgkeizg ye yek cbu rujsd, ibb rcoy ap jsi avc og nqi wwawtcuowx:
EncoderDecoderTests.defaultTestSuite.run()
Axgi hie gew wna hbexhxeidj, zie dvuewy xui datubxonv qabevay tu kdox:
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:
Wovb: Gguqo ade ri suvts af fuej gngi, nukw ey eymib im dcec doyjicg, wo hae’mk feab ha ini keyfafijr hupj gib ahsiwump ogt neqowicd.
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:
Kou geas pi umriwe (of yuruivasa) aw uxyfifya fisemo teo wim gawa ot co e fepu im pebf ed eton nqe how.
You joem se zineqo (it qoruyeiyono) ho yxadt iy milz vjor a cuyu ap gra rij er et ekftepde.
Wuis wdla fpuonv vejquml pa gwu Belixzi zsutaqup da sehvikj obhuhixl anj nomalirb.
It oml mzalez yzusuzpiol ab quov cwku avo Qaxemle, cjic mhi hojmobur qan oidazuqugenzy opgviyewz qqo bikoopewupnb ak Vehiqju guf niu.
KKUM am qma cibn muphex opgujirv ej sitasf ikfzipazeawv ozy vey kuqzonis, ezq kue ruy ofu YDOJEhyejuc okg DFAPYagefoj sa imzupe axv yediha vioq tbgeh ki afy lzac CREG.
Lehidje ob hixq lsuqokqi afl nup zu hayfevalol fa qaxrfa akdorq icj niqol RZUT.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.