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.

A0b320a5ad7c553eb90070d1a968aab3?s=128

Kaitlin Mahar

September 10, 2019
Tweet

Transcript

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

    MongoDB @k__mahar @kmahar
  2. What is encoding? Encoder Swift type External representation etc... Scalar

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

    (e.g. Int) struct class enum
  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
  5. Swift 4 introduced a standardized approach to encoding and decoding.

    How does it actually work?
  6. Basic Usage

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

    Encoder.
  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.
  9. • Automatic conformance if all properties are Encodable • Types

    can provide custom implementations • Format agnostic: write it once, works with any Encoder!
  10. A Decodable type knows how to initialize by reading from

    a Decoder.
  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.
  12. • Automatic conformance if all properties are Decodable • Types

    can provide custom implementations • Write it once, works with any Decoder
  13. None
  14. … and that's it! Making Types Codable

  15. Using Encoders and Decoders

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

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

  18. Using A Decoder JSONDecoder Decodable value of type T Type

    to decode to, and UTF-8 encoded Data
  19. Using A Decoder Data we got from encoding

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

  21. { "firstName":"Roscoe", "color":"orange" } Q: What if I want to

    rename a key? { "name":"Roscoe", "color":"orange" }
  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
  23. Renaming a key { "firstName":"Roscoe", "color":"orange" }

  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?
  25. A: the Encodable.encode method

  26. Encoder Single Value Unkeyed Keyed one value sequence of values

    key/value pairs provides containers: views into storage
  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
  28. So how are these containers used?

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

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

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

  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?
  33. Encoder KeyedEncodingContainer name color "roscoe" "orange"

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

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

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

    } ] }
  37. Encoder KeyedContainer name cats "Kaitlin" Unkeyed KeyedContainer name color "Chester"

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

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

    KeyedContainer KeyedContainer name color "Chester" "tan"
  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
  41. KeyedContainer name color "Roscoe" "orange" Unkeyed Encoder name cats "Kaitlin"

    KeyedContainer KeyedContainer name color "Chester" "tan" Calls Array<Cat>.encode(to: self)
  42. KeyedContainer name color "Roscoe" "orange" Unkeyed KeyedContainer name color "Chester"

    "tan" Encoder name cats "Kaitlin" KeyedContainer Array<Cat> Cat Again, compiler and encoder do this for you!
  43. { "name":"Kaitlin", "cats":[ "Chester", "Roscoe" ] } "Chester" "Roscoe" UnkeyedContainer

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

    Encoder name cats "Kaitlin" KeyedContainer A: Single value containers Single value containers!
  45. Flattening data + compiler generated CatOwner.encode no CodingKeys needed!

  46. { "name":"Kaitlin", "cats":[ "Chester", "Roscoe" ] } "Chester" "Roscoe" UnkeyedContainer

    Encoder name cats "Kaitlin" KeyedContainer Flattening data
  47. Weren't we also talking about decoding?

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

  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
  50. Super Advanced Usage: Writing Your Own Encoders and Decoders

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

  52. Encoder != Encoder

  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
  54. JSONEncoder Structure _JSONEncoder: Encoder _JSONEncodingStorage JSONEncoder private

  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
  56. Get top-level object from privateEncoder and pass it to JSONSerialization

  57. Decoder != Decoder

  58. JSONDecoder Structure _JSONDecoder: Decoder _JSONDecodingStorage JSONDecoder private

  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
  60. Use JSONSerialization to create object from data

  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?
  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.
  63. Thank you! Kaitlin Mahar Software Engineer @ MongoDB @k__mahar @kmahar