What is encoding?
Encoder
Swift type External
representation
etc...
Scalar (e.g. Int)
struct
class
enum
Slide 3
Slide 3 text
What is encoding?
JSON
Encoder
Slide 4
Slide 4 text
What is decoding?
Decoder
Swift type External
representation
etc...
Scalar (e.g. Int)
struct
class
enum
Slide 5
Slide 5 text
What is decoding?
JSON
Decoder
Slide 6
Slide 6 text
Swift 4 introduced a
standardized approach to
encoding and decoding.
How does it actually work?
Slide 7
Slide 7 text
Why does it matter?
● Customize how your types are encoded and decoded
○ Omit or rename properties, flatten nested structs...
● Write your own encoder and/or decoder
○ MongoDB driver
■ BSONEncoder: Swift type → MongoDB document
■ BSONDecoder: MongoDB document → Swift type
Slide 8
Slide 8 text
What we'll talk about
● The public API
● Internals
● Limitations
Slide 9
Slide 9 text
The Public API
Slide 10
Slide 10 text
An Encodable type knows how to
write itself to an Encoder.
Slide 11
Slide 11 text
● Automatic conformance if all properties are Encodable
● Types can provide custom implementations
● Format agnostic: write it once, works with any Encoder!
Slide 12
Slide 12 text
A Decodable type knows how to
initialize by reading from a Decoder.
Slide 13
Slide 13 text
● Automatic conformance if all properties are Decodable
● Types can provide custom implementations
● Write it once, works with any Decoder
Slide 14
Slide 14 text
No content
Slide 15
Slide 15 text
Types With Built-In Codable Support
● Numeric types
● Bool
● String
● If the values they contain are Encodable / Decodable:
○ Array
○ Set
○ Dictionary
○ Optional
● Common Foundation types: URL, Data, Date, etc.
Slide 16
Slide 16 text
… and that's it!
Making Types Codable
Slide 17
Slide 17 text
Using Encoders and Decoders
Slide 18
Slide 18 text
Using An Encoder
JSONEncoder
Encodable
value of type T
UTF-8
encoded Data
Slide 19
Slide 19 text
Using An Encoder
Slide 20
Slide 20 text
Why doesn't the API match the Encodable protocol?
Slide 21
Slide 21 text
_JSONEncoder:
Encoder
JSONEncoder
Why doesn't the API match the Encodable protocol?
private
● Not dictated by any protocol
● Allows setting top-level options
(e.g. DateEncodingStrategy)
● Simple API, just one method
● Implements Encoder protocol
● Actually does the encoding work
● More complex API (stay tuned)
Slide 22
Slide 22 text
Using A Decoder
JSONDecoder
Decodable
value of type T
Type to decode
to, and UTF-8
encoded Data
Slide 23
Slide 23 text
Using A Decoder
Data we got
from encoding
Slide 24
Slide 24 text
_JSONDecoder:
Decoder
JSONDecoder
Why doesn't the API match the Decodable protocol?
private
Slide 25
Slide 25 text
Ok, so… what do these
protocols actually require?
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
In code...
SingleValueEncodingContainer
UnkeyedEncodingContainer
KeyedEncodingContainer
Encoder
Slide 28
Slide 28 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
CodingKey
Requirements:
● Initializable by String
and/or Int
● Must have a String
representation
● May have an Int
representation
Synthesized conformance for enums!
Slide 34
Slide 34 text
Encoder
Single
Value
Unkeyed Keyed
Unkeyed Keyed Unkeyed Keyed
recursive case
Encoder
KeyedEncodingContainer
name color
"Chester" "tan"
Slide 39
Slide 39 text
Encoder
KeyedEncodingContainer
name color
"Chester" "tan"
Slide 40
Slide 40 text
Encoder
KeyedEncodingContainer
name color
"Chester" "tan"
Compiler generated!
Slide 41
Slide 41 text
But what if we don't want the defaults?
Encoder
KeyedEncodingContainer
firstName color
"Chester" "tan"
We can override just CodingKeys to
rename keys but keep the default
encode(to:) implementation.
Slide 42
Slide 42 text
But what if we don't want the defaults?
We can override just CodingKeys to
omit keys altogether.
Encoder
KeyedEncodingContainer
name
"Chester"
Slide 43
Slide 43 text
But what if we don't want the defaults?
Encoder
KeyedEncodingContainer
name color
"chester" "orange"
Or we can override encode(to:) to
further customize behavior.
Slide 44
Slide 44 text
Let's make things more complicated...
Slide 45
Slide 45 text
Encoder
KeyedContainer
name cats
"Kaitlin"
Unkeyed
KeyedContainer
name color
"Chester" "tan"
KeyedContainer
name color
"Roscoe" "orange"
CatOwner
cats
cats[1]
cats[0]
Slide 46
Slide 46 text
KeyedContainer
name color
"Roscoe" "orange"
Unkeyed
Encoder
name cats
"Kaitlin"
KeyedContainer
KeyedContainer
name color
"Chester" "tan"
Slide 47
Slide 47 text
KeyedContainer
name color
"Roscoe" "orange"
Unkeyed
KeyedContainer
name color
"Chester" "tan"
Encoder
name cats
"Kaitlin"
KeyedContainer
Slide 48
Slide 48 text
Alternatively...
Slide 49
Slide 49 text
KeyedContainer
name color
"Roscoe" "orange"
Unkeyed
name color
"Chester" "tan"
Encoder
name cats
"Kaitlin"
KeyedContainer
KeyedContainer
Again, compiler generated!
Slide 50
Slide 50 text
Weren't we also talking
about decoding?
Slide 51
Slide 51 text
Encoding: putting values into containers
Decoding: taking values out of containers
Slide 52
Slide 52 text
Decoder
Single
Value
Unkeyed Keyed
Unkeyed Keyed Unkeyed Keyed
recursive case
Slide 53
Slide 53 text
In code...
Decoder
SingleValueDecodingContainer
UnkeyedDecodingContainer
KeyedDecodingContainer
Slide 54
Slide 54 text
Decoding containers support retrieving three types of values.
nil
Bool, String, Double, Float
all Int and UInt types
Decodable type
base case 1: nil
base case 2:
primitives
recursive case
Decoder
KeyedDecodingContainer
name color
"Chester" "tan"
Slide 59
Slide 59 text
Decoder
KeyedDecodingContainer
name color
"Chester" "tan"
Slide 60
Slide 60 text
Decoder
KeyedDecodingContainer
name color
"Chester" "tan"
Compiler generated!
Slide 61
Slide 61 text
Public API Takeaways
● Types opt in by conforming to Encodable and/or Decodable
● Many types get conformance for free, but can customize when
needed
● Public types (e.g. JSONEncoder) do *not* implement the
corresponding protocols!
● Encoders and Decoders used container-based APIs for
reading from/writing to storage
● The Encoder and Decoder APIs are very similar!
Storage
In what format do I store values while I am still in the middle
of encoding/decoding?
● JSON: NS* types that work with JSONSerialization.
● PropertyList: NS* types that work with PropertyListSerialization.
● BSON: types conforming to BsonValue that work with MongoDB documents.
How do I track where I am in the maze of nested containers?
● JSON, BSON, PropertyList: stack with whatever is backing the current
container on top
Slide 65
Slide 65 text
4 more small but important things to note:
1. SingleValueXContainer is often just implemented via an
extension of the private Encoder type.
2. The Encoder must enforce that only one value is written to a
SingleValueEncodingContainer.
3. There are various bookkeeping requirements on the protocols for
tracking the path of keys taken so far.
4. There are also superEncoder and superDecoder requirements for
use with classes.
Slide 66
Slide 66 text
Limitations
● Lots of boilerplate required for all of the containers, and
the different encode and decode methods that they
support
● Depending on design, you may end up duplicating a lot of
code from JSONEncoder etc.
● Types cannot be extended to become Decodable (but
should it be possible?)
Slide 67
Slide 67 text
In conclusion...
● The API makes Codable conformance trivial in many cases, but also allows
for very advanced customization when needed.
● The API makes it possible to write Encoders and Decoders that don't know
what the types using them look like, and makes it possible for types to write
encode methods without caring about the actual output format!
● Investigating the standard library source code and writing your own
encoder/decoder reveals some hidden requirements.