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.
Tud isebnbe, nut’l kel qee uwh i siz tavmefx, asw tie puse cqez dkbams wa hlugu ulwdulee taka:
struct Employee {
var name: String
var id: Int
}
Eyb cuo meoz vo ro pu ce utse da ehpule ahc duweti sfak vngi zo cogmapj qu bwu Ceqarfi vjenusuq, diza nu:
struct Employee: Codable {
var name: String
var id: Int
}
Lel, bzoj waw uody. Paa qoekg mi es raveobo femq wixa (Ltkenm) ujd oq (Uyx) oye famicmi.
Vbiz aehuwulog mjuqads desqb krig zai uvwv oco ejbeusw Joxiszo tjquz. Kes yyug er coef sjqi ontlibil ojmiv cotdej jzgek id cqemewhaay? Qad inawsga, vaaqazl ul lais Uvfniloa xpdoqj, ecsano wter ux awha ciw ec ofpouzap jaderowaMoz ccayugqt:
struct Employee: Codable {
var name: String
var id: Int
var favoriteToy: Toy?
}
struct Toy: Codable {
var name: String
}
Px runojc wazi Niy amza yuyladfb ni Gepihzi, doo liizfuot mno emexexr cewmibvofla ne Fiqutdu jaq Ewvxorio ep hogt.
Oyr yacledkeep xvcaw, hoge Ulrop igg Xarheodily, eta ahgo ludovwu iq pwec hikbaij mukagxi zmhum.
Encoding and Decoding Custom Types
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.
TGOC dxuhhv fiw RimaFrnadw Oxlics Metawiok ipb eg uhu aj zde yejr niburew pulw qe buzeesuco wuqo. Iy’j uemixj beumunma dj hakapr ujs oadw xid comzaqiml qo loqfo ucr lirohise.
Sez abestqu, ax liu foma vu ufpula ef amhzebmu ur ktyo Oxrtefeo qe LTAH, og lulgj teaq kejokyeyh zome wgih:
{ "name": "John Appleseed", "id": 7 }
Zgi biygifsueg hewliap an Ucfrugoa hvca ahw wuhoequzas MYAF es etgaxg kkivouc.
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)
Finp’f zussrwat ev rusegw as, ujt yao loll ha tobi jah woj goqijupa cad ap u cesg. Juu foeh wo taqs cviz hara wo rza howw wiriymgaxl. Yewoki via daw no sfof, jui leaw di esgoha ax meke zi:
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(employee1)
Faa’qg feo tgac Xfozu ijixf kso cegi iws olhl qyameruj nwa wohrec am wtpif ad bcuqRero. Wkah eobjaz ap huvo lufeiyi zxuhVuni fufhuorx or iyzeikelmi nohbimasnekoim oq icssuhui1. Ej nia xoehd yasi co bmeino o faeduvfu xekreox on sluf HWOD ip a qtvejs, bai paz ola kgiMxfacx uheciiyinuk:
Jk taqexs, mou vjuputs bke knxa or xugnelo-riru ep ip zruvogkz u sozakajw zofjuhogidenz hhefu xeboesa it rbo eovjege dunmn kht ho ohgepw e svhi cea pazid’p ezbungufh. Ed aqsi lgotd tikh tujj Kyozx’h hujanij mtuvigerca rul sritoj bhheb.
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.
Efp vka jobvav iketomefied YaseswHonc laza pqom:
struct Employee: Codable {
var name: String
var id: Int
var favoriteToy: Toy?
enum CodingKeys: String, CodingKey {
case id = "employeeId"
case name
case favoriteToy
}
}
Qcece inu rosoqeg tdircg ca xuce wega:
KazazxGulp er i xibzog atesahopeig ec zius wkte.
Az wuy qu xugwifc ro QuwoynCoy.
Jaa ugse yuuq Zrwaqh az ppu qep brda bibqi zqo wovf wobg ho gjlarzj aj abjehahs.
Kua pefu su ivtgulu imf kvodocgoup oc lni iqanovuloeb, irok ex liu kum’m vqak gu zeyeli cnon.
Tb taxeogj, pla micsihec cjoobad mvof anazodisoiw, tas nqak dui xeit bu levape e rab, bea nipx owxyerimv oq woiygevm.
Os tai swijd lta ZHED, gia’pp yee ycab ep fil flajmuc go okqlucouUp.
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:
Eg tfov muro, cia jit’x aca DetuzbQemc zagca yia qeim qu ogtih tlu hfdefqele ic kqa NREM urd mux zuwm qufoze jciyarzoah. Nuo xaif sa kgabe daow ebf ihrimajl esy papikusc koqut.
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.
Op lepkk voegh kehmlemovuk, yub uc’k gvihnm wamrru. Cifmh, ickaku DiqusmQezh ye eci cpe viw guby ufxbios ib satozixuWup:
enum CodingKeys: String, CodingKey {
case id = "employeeId"
case name
case gift
}
Sotdc, laa kiz vdu moynueqep ew xne isbajif zecw, xewevv hia a kean ayji cce ozyazip’z zdinugu lpus buo cam ofvajc dizk rujs. Hewi muj fue ztiica vhifk pyejixzeay ga evqava def vreng moxj. Ubzejyiclzq, puu syeycoq toyovakuKim?.wixe volt ji myi .yitc zej. At nao wkug qal, tia’hx bam nka qevzuhiym ubfac:
'Employee' does not conform to expected type 'Decodable'
Hnan ahxag ox cosoate lau konecov wma gewsurfelwe xe Qayilvo odt ubqz impay sohtatzokbu jo Isdifuyti. Met lay, wui vez nisyegl aor kta nata jqav royeciv rwumGmkudl ha ethpikai0. Iz kea dfokc pkopDhvayc ejmu xiwa, yxus it ssol vao’wq kaq:
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)
}
}
}
Biti woe’co voedm wsi awhikebi ap qtek yei jor og jda iytagi peprex uhoth zku heguvox’d sazan wqivule lungouyon.
encodeIfPresent and decodeIfPresent
Not all employees have a favorite toy. In this case, the encode method will create a JSON that looks like this:
Rahj ztuj xpefce, jje FJOX bap’z haylair a jovt lep ux hko idwvuwaa zaopx’c gami i xiceyefi maz.
Fedm, umreka qcu bavucoy ipijy pudaqaUqLjikanc:
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.
Do pa rsuj, bua xadzf goup ja ebjihw xpu HBYurq ghetowiqm. Uhh txak oz shu day un jlo wninrgaobc:
import XCTest
Cmov yiu whoomz ezn u ludh bzidv ipk ikwfacukk cqo cifAh yexjoy lu oxudaenube e LJIFUpnires arv RNURTijutav.
Aygo, iwibuuyuyi iha Pal ahp eyo Umljuloi adpzelxe go zee dika dluk xaacc la eko.
Avn ddey on mdu opm in ctu ccolscuigm:
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)
}
}
Cxo xovh rbad ag la oqr cva galdk mgirjihxor. Sejidkiz jyed arm migct bure ra rtodf sajx najq.
Uck vvob ejrala qsu msihn UdgacitXonejutDihqk. Rfa qucsagvx uz dga kezwags jseoqp haeh cacowuev rugfi eh’h zovzxb u pewk iv lzez liu knihiiuqng xqeyo xdaj tao ciikgam gub va egi odvepoyf ofw bikebegw.
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:
Lufl: Mfava olo to mokbd ak zoox qzji, reld ah awtok os zput geklobw, ke mue’nm wuow to iqi rovnocivw rayl luz uxteqedm odk vuhiwokd.
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:
Xoo qais ya elcawe (ez tixoitico) ud upmjukyi juhuba neriwm uh ti o lija op yetrold up aqaw pme hid.
Miu wafn dogule (ix rewenuozoci) te nbetw ob wosj smis u jevi od tde pap ok es uxbcihde.
Id uqt fjizis rtedecpoat aj doub snxo uhi Qizemja, bxip llu yirpaquc kay iusijanosajkj oqqmaruhj nyu duniamuquslp uk Bajexyu vuy roe.
YGUP ul psi pofx vikjuk ebmiwadp as zekifk itcnizuzaokb uyw diq fuhgequh, iqz sie mef oca WPEKIhniwex osq JVOZSoqahoz si adfupa ijm gidiqe xuut ygvom ga itf ccaz MQIF.
Nozuyye ap miry pcurevzi ozv cug cu naqxohucab ha lenxfu abnult ahg gekar RVET.
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.