Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Encoding and Decoding in Swift

Encoding and Decoding in Swift

Ever wonder what’s actually going on inside JSONEncoder and JSONDecoder? Why doesn't JSONEncoder conform to the Encoder protocol? What's a CodingKey, and what are all of those "containers" for?

This talk will be a deep dive into encoding and decoding in Swift: what all of the related protocols are, how they fit together, and how to use them. You will come away from this talk ready to customize how your Swift types are encoded and decoded, and with the knowledge necessary to start writing encoders and decoders of your own.

Kaitlin Mahar

September 10, 2019
Tweet

More Decks by Kaitlin Mahar

Other Decks in Programming

Transcript

  1. Encoding and Decoding
    in Swift
    Kaitlin Mahar
    Software Engineer @ MongoDB
    @k__mahar @kmahar

    View Slide

  2. What is encoding?
    Encoder
    Swift type External
    representation
    etc...
    Scalar (e.g. Int)
    struct
    class
    enum

    View Slide

  3. What is decoding?
    Decoder
    Swift type External
    representation
    etc...
    Scalar (e.g. Int)
    struct
    class
    enum

    View Slide

  4. 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

    View Slide

  5. Swift 4 introduced a
    standardized approach to
    encoding and decoding.
    How does it actually work?

    View Slide

  6. Basic Usage

    View Slide

  7. An Encodable type knows how to
    write itself to an Encoder.

    View Slide

  8. 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.

    View Slide

  9. ● Automatic conformance if all properties are Encodable
    ● Types can provide custom implementations
    ● Format agnostic: write it once, works with any Encoder!

    View Slide

  10. A Decodable type knows how to
    initialize by reading from a Decoder.

    View Slide

  11. 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.

    View Slide

  12. ● Automatic conformance if all properties are Decodable
    ● Types can provide custom implementations
    ● Write it once, works with any Decoder

    View Slide

  13. View Slide

  14. … and that's it!
    Making Types Codable

    View Slide

  15. Using Encoders and Decoders

    View Slide

  16. Using An Encoder
    JSONEncoder
    Encodable
    value of type T
    UTF-8
    encoded Data

    View Slide

  17. Using An Encoder
    {
    "name":"Roscoe",
    "color":"orange"
    }

    View Slide

  18. Using A Decoder
    JSONDecoder
    Decodable
    value of type T
    Type to decode
    to, and UTF-8
    encoded Data

    View Slide

  19. Using A Decoder
    Data we got
    from encoding

    View Slide

  20. Advanced Usage:
    Customizing How
    Your Types are
    Encoded/Decoded

    View Slide

  21. {
    "firstName":"Roscoe",
    "color":"orange"
    }
    Q: What if I want to rename a key?
    {
    "name":"Roscoe",
    "color":"orange"
    }

    View Slide

  22. 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

    View Slide

  23. Renaming a key
    {
    "firstName":"Roscoe",
    "color":"orange"
    }

    View Slide

  24. 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?

    View Slide

  25. A: the Encodable.encode method

    View Slide

  26. Encoder
    Single
    Value
    Unkeyed Keyed
    one value sequence of values key/value pairs
    provides containers:
    views into storage

    View Slide

  27. 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

    View Slide

  28. So how are these
    containers used?

    View Slide

  29. Encoder
    KeyedEncodingContainer
    name color
    "Roscoe" "orange"

    View Slide

  30. Encoder
    KeyedEncodingContainer
    name color
    "Roscoe" "orange"

    View Slide

  31. Encoder
    KeyedEncodingContainer
    name color
    "Roscoe" "orange"
    Compiler-generated defaults

    View Slide

  32. 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?

    View Slide

  33. Encoder
    KeyedEncodingContainer
    name color
    "roscoe" "orange"

    View Slide

  34. What if I have custom
    types nested within
    other types?

    View Slide

  35. Let's make things more complicated...

    View Slide

  36. {
    "name":"Kaitlin",
    "cats":[
    {
    "name":"Chester",
    "color":"tan"
    },
    {
    "name":"Roscoe",
    "color":"orange"
    }
    ]
    }

    View Slide

  37. Encoder
    KeyedContainer
    name cats
    "Kaitlin"
    Unkeyed
    KeyedContainer
    name color
    "Chester" "tan"
    KeyedContainer
    name color
    "Roscoe" "orange"
    CatOwner
    cats
    cats[1]
    cats[0]

    View Slide

  38. KeyedContainer
    name color
    "Roscoe" "orange"
    Unkeyed
    Encoder
    name cats
    "Kaitlin"
    KeyedContainer
    KeyedContainer
    name color
    "Chester" "tan"

    View Slide

  39. KeyedContainer
    name color
    "Roscoe" "orange"
    Unkeyed
    Encoder
    name cats
    "Kaitlin"
    KeyedContainer
    KeyedContainer
    name color
    "Chester" "tan"

    View Slide

  40. 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

    View Slide

  41. KeyedContainer
    name color
    "Roscoe" "orange"
    Unkeyed
    Encoder
    name cats
    "Kaitlin"
    KeyedContainer
    KeyedContainer
    name color
    "Chester" "tan"
    Calls Array.encode(to: self)

    View Slide

  42. 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!

    View Slide

  43. {
    "name":"Kaitlin",
    "cats":[
    "Chester",
    "Roscoe"
    ]
    }
    "Chester" "Roscoe"
    UnkeyedContainer
    Encoder
    name cats
    "Kaitlin"
    KeyedContainer
    Q: What if I want to flatten my data?

    View Slide

  44. {
    "name":"Kaitlin",
    "cats":[
    "Chester",
    "Roscoe"
    ]
    }
    "Chester" "Roscoe"
    UnkeyedContainer
    Encoder
    name cats
    "Kaitlin"
    KeyedContainer
    A: Single value containers
    Single value
    containers!

    View Slide

  45. Flattening data
    + compiler generated CatOwner.encode
    no CodingKeys needed!

    View Slide

  46. {
    "name":"Kaitlin",
    "cats":[
    "Chester",
    "Roscoe"
    ]
    }
    "Chester" "Roscoe"
    UnkeyedContainer
    Encoder
    name cats
    "Kaitlin"
    KeyedContainer
    Flattening data

    View Slide

  47. Weren't we also talking
    about decoding?

    View Slide

  48. Decoder
    KeyedDecodingContainer
    name color
    "Chester" "tan"
    Compiler generated defaults

    View Slide

  49. 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

    View Slide

  50. Super Advanced
    Usage: Writing Your
    Own Encoders and
    Decoders

    View Slide

  51. Why doesn't the API match the Encodable protocol?

    View Slide

  52. Encoder != Encoder

    View Slide

  53. _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

    View Slide

  54. JSONEncoder Structure
    _JSONEncoder:
    Encoder
    _JSONEncodingStorage
    JSONEncoder
    private

    View Slide

  55. 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

    View Slide

  56. Get top-level object from privateEncoder
    and pass it to JSONSerialization

    View Slide

  57. Decoder != Decoder

    View Slide

  58. JSONDecoder Structure
    _JSONDecoder:
    Decoder
    _JSONDecodingStorage
    JSONDecoder
    private

    View Slide

  59. 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

    View Slide

  60. Use JSONSerialization to create object from data

    View Slide

  61. 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?

    View Slide

  62. 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.

    View Slide

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

    View Slide