Encoding and Decoding
in Swift
Kaitlin Mahar
Software Engineer @ MongoDB
@k__mahar @kmahar
Slide 2
Slide 2 text
What is encoding?
Encoder
Swift type External
representation
etc...
Scalar (e.g. Int)
struct
class
enum
Slide 3
Slide 3 text
What is decoding?
Decoder
Swift type External
representation
etc...
Scalar (e.g. Int)
struct
class
enum
Slide 4
Slide 4 text
Why would I want to encode and decode data?
● Allows data transfer in and out of your application
○ Communicating with a REST API via JSON
○ Reading from and writing to a database
○ Importing and exporting data from files
Slide 5
Slide 5 text
Swift 4 introduced a
standardized approach to
encoding and decoding.
How does it actually work?
Slide 6
Slide 6 text
Basic Usage
Slide 7
Slide 7 text
An Encodable type knows how to
write itself to an Encoder.
Slide 8
Slide 8 text
Types With Built-In Encodable Support
● Numeric types
● Bool
● String
● If the values they contain are Encodable:
○ Array
○ Set
○ Dictionary
○ Optional
● Common Foundation types: URL, Data, Date, etc.
Slide 9
Slide 9 text
● Automatic conformance if all properties are Encodable
● Types can provide custom implementations
● Format agnostic: write it once, works with any Encoder!
Slide 10
Slide 10 text
A Decodable type knows how to
initialize by reading from a Decoder.
Slide 11
Slide 11 text
Types With Built-In Decodable Support
● Numeric types
● Bool
● String
● If the values they contain are Decodable:
○ Array
○ Set
○ Dictionary
○ Optional
● Common Foundation types: URL, Data, Date, etc.
Slide 12
Slide 12 text
● Automatic conformance if all properties are Decodable
● Types can provide custom implementations
● Write it once, works with any Decoder
Slide 13
Slide 13 text
No content
Slide 14
Slide 14 text
… and that's it!
Making Types Codable
Slide 15
Slide 15 text
Using Encoders and Decoders
Slide 16
Slide 16 text
Using An Encoder
JSONEncoder
Encodable
value of type T
UTF-8
encoded Data
Slide 17
Slide 17 text
Using An Encoder
{
"name":"Roscoe",
"color":"orange"
}
Slide 18
Slide 18 text
Using A Decoder
JSONDecoder
Decodable
value of type T
Type to decode
to, and UTF-8
encoded Data
Slide 19
Slide 19 text
Using A Decoder
Data we got
from encoding
Slide 20
Slide 20 text
Advanced Usage:
Customizing How
Your Types are
Encoded/Decoded
Slide 21
Slide 21 text
{
"firstName":"Roscoe",
"color":"orange"
}
Q: What if I want to rename a key?
{
"name":"Roscoe",
"color":"orange"
}
Slide 22
Slide 22 text
A: Use CodingKeys
● Nested type that specifies the keys that will be used for encoding
● Compiler generated, but custom implementation can be provided
Compiler-
generated
default
Slide 23
Slide 23 text
Renaming a key
{
"firstName":"Roscoe",
"color":"orange"
}
Slide 24
Slide 24 text
Q: What if I want to modify properties as I
encode them?
{
"name":"roscoe",
"color":"orange"
}
{
"name":"Roscoe",
"color":"orange"
}
e.g. Convert a string to lowercase?
Slide 25
Slide 25 text
A: the Encodable.encode method
Slide 26
Slide 26 text
Encoder
Single
Value
Unkeyed Keyed
one value sequence of values key/value pairs
provides containers:
views into storage
Slide 27
Slide 27 text
Encoding containers support storing three types of values.
nil
Bool, String, Double, Float
all Int and UInt types
Encodable type
base case 1: nil
base case 2:
primitives
recursive case
Slide 28
Slide 28 text
So how are these
containers used?
Slide 29
Slide 29 text
Encoder
KeyedEncodingContainer
name color
"Roscoe" "orange"
Slide 30
Slide 30 text
Encoder
KeyedEncodingContainer
name color
"Roscoe" "orange"
Slide 31
Slide 31 text
Encoder
KeyedEncodingContainer
name color
"Roscoe" "orange"
Compiler-generated defaults
Slide 32
Slide 32 text
Q: What if I want to modify properties as I
encode them?
{
"name":"roscoe",
"color":"orange"
}
{
"name":"roscoe",
"color":"orange"
}
e.g. Convert a string to lowercase?
Slide 33
Slide 33 text
Encoder
KeyedEncodingContainer
name color
"roscoe" "orange"
Slide 34
Slide 34 text
What if I have custom
types nested within
other types?
Encoder
KeyedContainer
name cats
"Kaitlin"
Unkeyed
KeyedContainer
name color
"Chester" "tan"
KeyedContainer
name color
"Roscoe" "orange"
CatOwner
cats
cats[1]
cats[0]
Slide 38
Slide 38 text
KeyedContainer
name color
"Roscoe" "orange"
Unkeyed
Encoder
name cats
"Kaitlin"
KeyedContainer
KeyedContainer
name color
"Chester" "tan"
Slide 39
Slide 39 text
KeyedContainer
name color
"Roscoe" "orange"
Unkeyed
Encoder
name cats
"Kaitlin"
KeyedContainer
KeyedContainer
name color
"Chester" "tan"
Slide 40
Slide 40 text
Encoding containers support storing three types of values.
nil
Bool, String, Double, Float
all Int and UInt types
Encodable type
base case 1: nil
base case 2:
primitives
recursive case
Slide 41
Slide 41 text
KeyedContainer
name color
"Roscoe" "orange"
Unkeyed
Encoder
name cats
"Kaitlin"
KeyedContainer
KeyedContainer
name color
"Chester" "tan"
Calls Array.encode(to: self)
Slide 42
Slide 42 text
KeyedContainer
name color
"Roscoe" "orange"
Unkeyed
KeyedContainer
name color
"Chester" "tan"
Encoder
name cats
"Kaitlin"
KeyedContainer
Array
Cat
Again, compiler and encoder do this for you!
Slide 43
Slide 43 text
{
"name":"Kaitlin",
"cats":[
"Chester",
"Roscoe"
]
}
"Chester" "Roscoe"
UnkeyedContainer
Encoder
name cats
"Kaitlin"
KeyedContainer
Q: What if I want to flatten my data?
Slide 44
Slide 44 text
{
"name":"Kaitlin",
"cats":[
"Chester",
"Roscoe"
]
}
"Chester" "Roscoe"
UnkeyedContainer
Encoder
name cats
"Kaitlin"
KeyedContainer
A: Single value containers
Single value
containers!
Slide 45
Slide 45 text
Flattening data
+ compiler generated CatOwner.encode
no CodingKeys needed!
Slide 46
Slide 46 text
{
"name":"Kaitlin",
"cats":[
"Chester",
"Roscoe"
]
}
"Chester" "Roscoe"
UnkeyedContainer
Encoder
name cats
"Kaitlin"
KeyedContainer
Flattening data
Slide 47
Slide 47 text
Weren't we also talking
about decoding?
Slide 48
Slide 48 text
Decoder
KeyedDecodingContainer
name color
"Chester" "tan"
Compiler generated defaults
Slide 49
Slide 49 text
Customization Takeaways
● Use CodingKeys to customize which properties are
encoded/decoded, and what names they are encoded under
and decoded from
● Use custom encode(to:) and init(from:)
implementations to:
○ Transform data as you encode/decode it
○ Restructure your data
Slide 50
Slide 50 text
Super Advanced
Usage: Writing Your
Own Encoders and
Decoders
Slide 51
Slide 51 text
Why doesn't the API match the Encodable protocol?
Slide 52
Slide 52 text
Encoder != Encoder
Slide 53
Slide 53 text
_JSONEncoder:
Encoder
JSONEncoder
Why doesn't the API match the Encodable protocol?
private
Single encode method
Container API
More at https://tinyurl.com/encoder-protocol
JSONEncoder Structure
_JSONEncodingStorage
NSDictionary or NSArray
● NSArray if first container requested is unkeyed
● NSDictionary otherwise
● Container API is used to construct it
● Why use NS*?
○ JSONSerialization requires it
Slide 56
Slide 56 text
Get top-level object from privateEncoder
and pass it to JSONSerialization
JSONDecoder Structure
_JSONDecodingStorage
NSDictionary or NSArray
● NSArray if JSON array was provided
● NSDictionary if JSON object was provided
● Container API is used to read from it
● Why use NS*?
○ JSONSerialization requires it
Slide 60
Slide 60 text
Use JSONSerialization to create object from data
Slide 61
Slide 61 text
Limitations
● Not very performant
○ See https://tinyurl.com/benchmark-codable
● Lots of boilerplate/error prone in some cases
○ What if I have 20 properties and only want to omit
one?
Slide 62
Slide 62 text
Advantages
● The API makes Codable conformance trivial in many cases,
but also allows for very advanced customization when needed.
● The standardized approach makes it so any Encodable type
can be used with any Encoder, and any Decodable type can
be used with any Decoder.