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 full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  7. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  10. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  13. Using Encoders and Decoders

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  17. Using A Decoder
    Data we got
    from encoding

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  20. 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 full-size slide

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

    View full-size slide

  22. 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 full-size slide

  23. A: the Encodable.encode method

    View full-size slide

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

    View full-size slide

  25. 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 full-size slide

  26. So how are these
    containers used?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  30. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  38. 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 full-size slide

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

    View full-size slide

  40. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  45. Weren't we also talking
    about decoding?

    View full-size slide

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

    View full-size slide

  47. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  50. Encoder != Encoder

    View full-size slide

  51. _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 full-size slide

  52. JSONEncoder Structure
    _JSONEncoder:
    Encoder
    _JSONEncodingStorage
    JSONEncoder
    private

    View full-size slide

  53. 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 full-size slide

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

    View full-size slide

  55. Decoder != Decoder

    View full-size slide

  56. JSONDecoder Structure
    _JSONDecoder:
    Decoder
    _JSONDecodingStorage
    JSONDecoder
    private

    View full-size slide

  57. 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 full-size slide

  58. Use JSONSerialization to create object from data

    View full-size slide

  59. 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 full-size slide

  60. 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 full-size slide

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

    View full-size slide