Slide 1

Slide 1 text

Swift's Encoder and Decoder Protocols 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 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

Slide 29

Slide 29 text

nil primitive Encodable SingleValueEncodingContainer

Slide 30

Slide 30 text

nil primitive Encodable SingleValueEncodingContainer UnkeyedEncodingContainer

Slide 31

Slide 31 text

Encodable primitive nil KeyedEncodingContainerProtocol

Slide 32

Slide 32 text

CodingKey KeyedEncodingContainer Encoder

Slide 33

Slide 33 text

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

Slide 35

Slide 35 text

Keyed Unkeyed UnkeyedEncodingContainer KeyedEncodingContainerProtocol Unkeyed Keyed

Slide 36

Slide 36 text

So how are these containers used?

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

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

Slide 55

Slide 55 text

SingleValueDecodingContainer nil primitive Decodable

Slide 56

Slide 56 text

nil primitive Decodable UnkeyedDecodingContainer

Slide 57

Slide 57 text

KeyedDecodingContainerProtocol nil primitive Decodable

Slide 58

Slide 58 text

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!

Slide 62

Slide 62 text

Underneath the Hood

Slide 63

Slide 63 text

Typical Structure _MyEncoder : Encoder storage private MyEncoder _MyDecoder : Decoder storage private MyDecoder

Slide 64

Slide 64 text

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.

Slide 68

Slide 68 text

Thank you! Kaitlin Mahar Software Engineer @ MongoDB @k__mahar @kmahar