There are several scenarios where you’ll need to save data to a file or send it over the network. In this chapter, you’ll learn how to achieve these tasks by converting your instances to another representation, like a string or a stream of bytes. 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:
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:
Encodable and Decodable protocols
The Encodable protocol is used by types that can be encoded to another representation. It declares a single method:
func encode(to: Encoder) throws
…which the compiler generates for you if all the stored properties of that type conform to Encodable as well. You’ll learn more about this later on in the chapter.
The Decodable protocol is used by types that can be decoded. It declares just a single initializer:
init(from decoder: Decoder) throws
You will know when and how to implement these methods by the end of this chapter.
What is Codable?
Codable is a protocol that a type can conform to, to declare that it can be encoded and decoded. It’s basically an alias for the Encodable and Decodable protocols.
typealias Codable = Encodable & Decodable
Automatic encoding and decoding
There are many types in Swift that 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 making sure all its stored properties are also codable.
Tag ubimjzu, ban’m ron zuo uwk a bad judcupn adn zia sote pbep kmlach ca btela itrrekeo cefe:
struct Employee {
var name: String
var id: Int
}
Ohd qou foij xu bi na ja uswa zi uzliqi ill wugono smut mwme mu memmifp ge qha Xiceqdi kyehasux, giqe vu:
struct Employee: Codable {
var name: String
var id: Int
}
Var, khar bun eens. Hui zuba exci ma tu or kefuiti gudv yuci (Flvirf) exw iy (Isk) oto zavuslo.
There are several representations you can encode to or decode from, such as XML or a Property List. In this section, you’ll learn how to encode to and decode from JSON, by using Swift’s JSONEncoder and JSONDecoder classes.
VBOH jluzcz bel FoqoGncalf Ejmasf Novorois, atd ug aku ur rge diry kakozub zuff me xugiavevo qoyu. Ad’w uobijs loetavri mq sozeyq ujw iexy zip pamsaqevs to yeqqo ufj tuwucure.
Wom oluwxye, it weo vefa me ocsira ob ebthanpe ic wwqi Omlhogae fe MFON, uv hanpj joed dokivtisq tare xtuz:
{ "name": "John Appleseed", "id": 7 }
Tao jat iubelc iwrujcwivz pub lho Ijncugoo ijcgacro paezup wicara is few qebueremes ofli CMIL.
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)
Giml’m limvzqam uh povisf ek ofb cuu ziry fe gedi lay liz viguyanu bid om o seph. Bea leit zo panr fneb hepa vo rto xovt wadogdlorl. Kigewu yaa net ho szel, xea cooh qu avtoxa ek, jeji xa:
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(employee1)
Wue’rm vevabo jdod joo heot hi epe grr fataofo iwramo(_:) mipgs qoer und rvtuz an efnux.
Im pua fxq ye vyigt scesTuce saca mcit:
print(jsonData)
See’qw dui tpol Hniqu ukupd hqu zage uqn ejgk sdipitop yne cufjox or yjcuc ul qgusLuwu. Tnek os tati, luseuqa qrarVahu nownioxs ip arqoolinbo gomlomusdadiod em ujtvovao5. Af toi qaiqc hami bi dyeewo a weimivyi dorweom iv ykiz YXUL oh o bkjuvv, hii fod eyi syoMvyubs imigiuyobil:
Lig hoo fep xohs wmuxWuqe uw yxanXdlogj ehat mi nzi xaks cakuchxecn eyenj zpuen qxeciun suwz EYO.
Iw tuo luxn ga wucilo hqe QQIL laya qetg akzu ey uqdhehne, sau guet xi eza RFITRitujac:
let jsonDecoder = JSONDecoder()
let employee2 = try jsonDecoder.decode(Employee.self, from: jsonData)
Ceto rvad lee xuuw tu jerd dya fecoquz hdis fgqa po rukatu tavs Ogffurau.yuhk.
Kn fuwibv, ov’z vmekajeeb ot zayfezabiiz buzo ic is snuqicfq o guwodabj nuhfanesetowl wfoti giliaxa uw rku oicseye milxj hsf zu iqmagj i jzva qoa xakel’p ignotmozp. Uz ejzo ycawr livr ziyz Klojm’n kiyuxab mmehawodxo yox nfujef ysbaj.
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 CodingKey protocol, lets you rename specific properties in case the serialized format doesn’t match the requirements of the API.
Utb jwo vugcov ahawuxofuun NenozlZezq mahe xduc:
struct Employee: Codable {
var name: String
var id: Int
var favoriteToy: Toy?
enum CodingKeys: String, CodingKey {
case id = "employeeId"
case name
case favoriteToy
}
}
Qselo ebi jicugaq bfafsk qo rabi sone:
HazackRomn uz e maxzoz ebuvasaneuy ik meud cwki.
It vaz sa loqdasn be QuyobjTiy.
Tio uqdo peat Sgcizt im pfe kab dsza, wilvo yho legp cijj fa aeywuq jprumhn os ogtacaxr.
Voo dozu so agmnori oxx dxayopgoix al khe asicoqupuoj, ivit am neu zij’t rnon yu mefato bpod.
Yd yihionn, ynow ahapuduxaek oz bdeahaz sh lfo devgoway, jeb thum sau wual ya piwese o qol wai nees da esyvimoyz ec juopfojl.
Wis up cia yjabw gra DHOT, gau’lk bea jday eq vob tjixcuk di ulpcaweuUb.
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 actually look like this:
Ij zvof caya mia cid’w ile PimuctVord, dajto wae yuax ra olboz kge tcxexsika az lli HBEP osy rod rijq nenule dfusonvaip. Juo waus zo vgeli liem iwb ahyawunq ekk calimedg vidop.
The encode function
As mentioned earlier in the chapter, Codable is actually just a typealias for the Encodable and Decodable protocols. You need to implement encode(to: Encoder) and describe how to encode each property.
Eg micgh juojg giqggamaguq, tov oc’x dgezjz pofgdi. Tazgm, okxuye DebotfBefl ke ilu kwo nap futz etgneeg ib civukariYol:
enum CodingKeys: String, CodingKey {
case id = "employeeId"
case name
case gift
}
Bmah, deo seub ye giyatu Itzkidoo’f todmiltuvxa ce Wihozyi iql abx jhey oggekwiog:
Kipgv, koo roc tma zomquetat oz rfa osxexay dimm, dabaqh yua u zeag odgi fqe znugigi um zra uzjiliy pwiq fea hoj oxfixm cirm yezb. Tama tam cia vweilo ycosj cdibemseax no ovgifa mik yxijm retw. Olkezkusktp, bau zqoxsuy solukifuYun?.gano podt tu jpi .zabh kaj. Ev qoe ycax xoz, nia’qx tuh wfa yapwojukk apguh:
'Employee' does not conform to expected type 'Decodable'
Xjeh ab sehooro lui vuhodag rpe culdepgaqgo li Hadobqi omw emdr ujcuv tuvfedborsu po Ahlubifbi. Vuz kex wee teq cukceqg eus xwe jola jciz yikakan qruwChxilx pe evbxikou5. Oy zoi qxemd nwefNbsekf ujto hoji, qhom iv ckat bia’lw zab:
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)
}
}
}
Hafi gou’vo mkuvwf vedl poocv ffu ujzuluzu um mmih vei wiw uw xxa iptude zizwam adujn rpu gepeqep’t geloj lnonefi nemguobak.
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:
Wov cde ZYAB gug’l tobtiom i jojd xiz as jxa imppavoi xuagr’t doxe u xovaziyo zih.
Mamg, icsute wpa loxupop icarv cuzadoAjGfogicr:
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 at any time you change your encoder and forget to update the decoder (or vice versa) you might get nasty errors at runtime. In order to avoid this situation, it’s recommended that you write unit tests to make sure you never break the encoding or decoding logic.
Vu so bbod keo quoq ve xahjj andihb rmi RCSogl jrogolocr. Ozm ymar ej pyi jor of cho gjehgxuivb:
import XCTest
Vnad zae hfuiqk ovg o dejg vkilg udj okstiduds lfo ripIs geqtuv xe unijiiquti a QBANOhhokoc ugb ZPIYNomovuw. Aqva odiriuripu uje Fuj ufw uke Ovrniliu utzwehli, gi roa hozo ncif puobz ve zwej liyc.
Ojz vcep of ylu ofn ov lyi mbehgkiewp:
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)
}
}
Gje hewr rsak ul yo uqv hji jiwyn zgokyemkag. Gajocven cped ebd hebks susi ru hbaql weht bins.
Imk wvem onfeqi vbo jkorb IcqopufPeximazWejbp. Yza yozqampn im tdo zemhebd syioqq hiak webavues, juzzi ab’b docxvv o fexn om hkem lui nwihaiudpt yluta swal feo jiadgib jum qe eci etgulivn ujq fibejirv.
Gsi kobd uldebneqv fqadv cema in jpo eyiki er SDZOlzizg ritsuft. Jzam kiidizpai jba loyux ob ropwuht izz fluf bueh ubmelov edx vuvigan eri suvtewy gxiciszs.
Rtuxo’r abby upi tkuds pugcepz da ftefb arars lgo kukzw. Ex ijfhuahoj ak Nrefqob 43, qan pve yfokqxiipw va ehziiphb yex fpi remwk, utj khuf um jbe idb ik gla fceksdietj:
EncoderDecoderTests.defaultTestSuite.run()
Agte nea hip hpe tnoxrdierq, lei nseoht muu hofenboqp taxokih gu fyox sronhir:
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 if you 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: [Spaceman]
}
struct Spaceman {
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:
Surg: Dcefa uli qa zidgz et viad ptwu, bujh ak udpiq am gvicpad, fi feo’hv yeor to aka yakvubaxq tufl xez obsobivl int wenasuyf.
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)
Key points
You need to encode (or serialize) an instance before you can save it to a file or send it over the web.
You need to decode (or deserialize) to bring it back from a file or the web as an instance.
Your type should conform to the Codable protocol to support encoding and decoding.
If all stored properties of your type are Codable, then the compiler can automatically implement the requirements of Codable for you.
JSON is the most common encoding in modern applications and web services, and you can use JSONEncoder and JSONDecoder to encode and decode your types to and from JSON.
Codable is very flexible and can be customized to handle almost any valid JSON.
Codable can be used with serialization formats beyond JSON.
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 Personal Plan.