Swift’s Codable protocol is a powerful feature that simplifies the process of encoding and decoding data between Swift types and external representations like JSON. This guide covers everything you need to know about working with Codable.

What is Codable?

Codable is a type alias that combines two protocols:

typealias Codable = Encodable & Decodable
  • Encodable: Allows converting Swift types to external formats (e.g., JSON)
  • Decodable: Allows converting external formats back into Swift types

Basic Usage

The simplest way to use Codable is with types that have Codable-compatible properties:

struct User: Codable {
    let id: Int
    let name: String
    let email: String
}

All standard Swift types like String, Int, Double, Bool, Date, URL, and arrays/dictionaries of these types automatically support Codable.

Custom Key Mapping

When your JSON keys don’t match your Swift property names, use CodingKeys:

struct User: Codable {
    let id: Int
    let fullName: String
    
    enum CodingKeys: String, CodingKey {
        case id
        case fullName = "full_name"
    }
}

Working with JSON

To convert between JSON and Swift types:

// Encoding to JSON
let encoder = JSONEncoder()
let data = try encoder.encode(user)

// Decoding from JSON 
let decoder = JSONDecoder()
let user = try decoder.decode(User.self, from: jsonData)

Advanced Features

Custom Encoding/Decoding

For complex cases, implement custom encoding/decoding:

struct CustomType: Codable {
    let value: String
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        value = try container.decode(String.self, forKey: .value)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(value, forKey: .value)
    }
}

Nested Containers

Handle nested JSON structures:

struct Response: Codable {
    let data: UserData
    
    struct UserData: Codable {
        let users: [User]
    }
}

Date Encoding Strategies

Configure how dates are encoded/decoded:

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601

let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601

Best Practices

  1. Start with automatic synthesis when possible
  2. Use CodingKeys for simple key mapping
  3. Implement custom encoding/decoding only when necessary
  4. Handle errors appropriately
  5. Use appropriate coding strategies for dates and data

Common Pitfalls

  • Forgetting to mark optional properties as optional
  • Not handling null values properly
  • Ignoring encoding/decoding errors
  • Using wrong date formats
  • Not considering nested containers for complex JSON

Error Handling

Always handle potential errors:

do {
    let user = try decoder.decode(User.self, from: data)
} catch DecodingError.keyNotFound(let key, _) {
    print("Missing key: \(key)")
} catch DecodingError.typeMismatch(_, let context) {
    print("Type mismatch: \(context.debugDescription)")
} catch {
    print("Other error: \(error)")
}

Performance Considerations

  • Use appropriate encoding/decoding strategies
  • Consider using JSONDecoder().keyDecodingStrategy for snake_case conversion
  • Cache encoders/decoders when possible
  • Use appropriate date encoding strategies
  • JSONSerialization
  • PropertyListEncoder/Decoder
  • Custom JSONEncoder/Decoder configurations
  • Working with APIs
  • Data persistence

References

/*:
 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)

 - - -
 */
//: # Automatic Decoding
import Foundation

let fruitData = """
{
  "id": "E621E1F8-C36C-495A-93FC-0C247A3E6E5F",
  "name": "Banana",
  "is_delicious": true,
  "images": [
    "http://www.whats4eats.com/files/ingredients-green-bananas-bunch-wikimedia-Rosendahl-4x3.jpg",
    "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRAthibvroFTdhqMUvC5IWFaxO1VyAfcxGj6L8mWzxPm3uzeFc0Pg"
  ],
  "nutrition": {
    "carbs": 23,
    "protein": 1.1
  },
  "availability": [
    {
      "name": "Grocery Store"
    },
    {
      "name": "Mega Store",
      "founded": 1522324117
    }
  ]
}
""".data(using: .utf8)!

struct Vegetable: Decodable {
    let identifier: UUID
    let name: String?
    let isDelicious: Bool
    let imagesURLs: [URL]?
    let nutrition: [String: Float]
    let availability: [Retailer]?
}

struct Retailer: Decodable {
    let name: String
    let website: URL?
    let founded: Date?
}

extension Vegetable {

/*:
- Note: Custom CodingKeys, if needed.\
To convert keys between snake case (snake_case) and camel case (camelCase) you can just use `JSONDecoder`'s `keyDecodingStrategy` and set it to `convertFromSnakeCase`
 */
    enum CodingKeys: String, CodingKey {
        case identifier = "id"
        case name
        case isDelicious
        case imagesURLs = "images"
        case nutrition
        case availability
    }
}

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
decoder.keyDecodingStrategy = .convertFromSnakeCase

let banana = try? decoder.decode(Vegetable.self, from: fruitData)

dump(banana)
/*:
 - - -

 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)
 */

/*:
 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)

 - - -
*/
//: # Automatic Encoding
import Foundation

struct Retailer: Codable {
    let name: String
    let website: URL?
    let founded: Date?
}

struct Vegetable: Codable {
    let identifier: UUID
    let name: String?
    let isDelicious: Bool
    let imagesURLs: [URL]?
    let nutrition: [String: Float]
    let availability: [Retailer]?
    let kingdom: String = "Plantae"
}

/*:
  You **can** declare custom `CodingKeys` object (typically an enumerator) with list of properties that must be included when instances of a codable type are encoded or decoded.

  - Important: About CodingKeys:\
  _1. CodingKeys enum has Raw Type `String` and conforms to `CodingKey` protocol\
  _2. Names of cases must match names of properties\
  _3. Properties you want to omit from encoding/decoding need to have default value\
  _4. To use custom keys for serialized data provide them as enum's raw values

    To convert keys between snake case (`snake_case`) and camel case (`camelCase`) you can just use `JSONEncoder`'s `keyEncodingStrategy` and set it to `convertToSnakeCase`
*/

extension Vegetable {

    enum CodingKeys: String, CodingKey {
        case identifier = "id"
        case name
        case isDelicious
        case imagesURLs = "images"
        case nutrition
        case availability
    }
}

let nearbyFamilyStore = Retailer(name: "StoreNo1", website: nil, founded: Date(timeIntervalSince1970: 1522324117))

let vegy = Vegetable(identifier: UUID(),
                     name: "Brocoli 🥦",
                     isDelicious: true,
                     imagesURLs: [URL(string: "http://harisabzi.com/wp-content/uploads/2017/10/Brocoli.jpg")!],
                     nutrition: ["protein": 5],
                     availability: [nearbyFamilyStore])

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.dateEncodingStrategy = .secondsSince1970
encoder.keyEncodingStrategy = .convertToSnakeCase

let data = try encoder.encode(vegy)
let JSON = try JSONSerialization.jsonObject(with: data)

print(JSON)

/*:
 - - -

 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)
 */
/*:
 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)

 - - -
 */
//: # Inheritance
//: ### Using classes for data models
//: ## Superclass
import Foundation

class Vehicle: Codable {
    let horsePower: Int

//: - Note: Mark Coding Keys private to hide them from subclasses:
    private enum CodingKeys: String, CodingKey {
        case horsePower
    }

    init(horsePower: Int) {
        self.horsePower = horsePower
    }

//: Provide required initializer (in this case it's not really needed. Thanks to default implementation, compiler would generate it for us):
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        horsePower = try container.decode(Int.self, forKey: .horsePower)
    }
//: Provide `encode(to:)` method (in this case it's not really needed):
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(horsePower, forKey: .horsePower)
    }
}

//: ## Subclass
class Motorcycle: Vehicle {
    let isChopper: Bool

//: - Note: Again Coding Keys are private, so they can have the same name as in super class
    private enum CodingKeys: String, CodingKey {
        case isChopper
        case generalInfo
    }

    init(horsePower: Int, isChopper: Bool) {
        self.isChopper = isChopper
        super.init(horsePower: horsePower)
    }

//: ### Subclass Decoding
    required init(from decoder: Decoder) throws {
//: First create container and decode subclass's properties into it
        let container = try decoder.container(keyedBy: CodingKeys.self)
        isChopper = try container.decode(Bool.self, forKey: .isChopper)

/*:
Than create decoder for decoding super from the container.\
It can be associated with the default key ("super") or custom key
*/
        let superDecoder = try container.superDecoder(forKey: .generalInfo)
        try super.init(from: superDecoder)
    }

//: ### Subclass Encoding
    override func encode(to encoder: Encoder) throws {
//: First create container for subclass's properties
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(isChopper, forKey: .isChopper)

//: Than call superclass's `encode(to:)` using superclass-ready encoder from the container
        try super.encode(to: container.superEncoder(forKey: .generalInfo))
//: As a result, subclass's properties are encoded to nested container
    }
}

//: ### Examples
let decoder = JSONDecoder()
let encoder = JSONEncoder()

let motorcycleData = """
{
    "generalInfo":     {
        "horsePower": 73
    },
    "isChopper": false
}
""".data(using: .utf8)!
let motorcycle = try decoder.decode(Motorcycle.self, from: motorcycleData)
//: ---
let ktm690Duke = Motorcycle(horsePower: 73, isChopper: false)
let ktm690DukeData = try encoder.encode(ktm690Duke)
let JSON = try JSONSerialization.jsonObject(with: ktm690DukeData)

print(JSON, terminator: "\n\n")
//: ---
//: ## Encoding to and decoding from shared container
class Train: Vehicle {
    let wagonsCount: Int

    private enum CodingKeys: String, CodingKey {
        case wagonsCount
    }

    init(horsePower: Int, wagonsCount: Int) {
        self.wagonsCount = wagonsCount
        super.init(horsePower: horsePower)
    }

//: To decode super and subclass's properties from single container, call `super.init(from: decoder)` after decoding subclass's properties.
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        wagonsCount = try container.decode(Int.self, forKey: .wagonsCount)

        try super.init(from: decoder)
    }

//: To encode super and subclass's properties to single container, call `super.encode(to: encoder)` before encoding subclass's properties.
    override func encode(to encoder: Encoder) throws {
        try super.encode(to: encoder)
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(wagonsCount, forKey: .wagonsCount)
    }
}

let unionPacific9000 = Train(horsePower: 4750, wagonsCount: 10)
let unionPacific9000Data = try encoder.encode(unionPacific9000)
let unionPacific9000JSON = try JSONSerialization.jsonObject(with: unionPacific9000Data)

print(unionPacific9000JSON)
//: ---
let trainData = """
{
    "horsePower": 4750,
    "wagonsCount": 10
}
""".data(using: .utf8)!
let train = try decoder.decode(Train.self, from: trainData)
/*:
 - - -

 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)
 */
/*:
 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)

 - - -
 */
//: # CodingUserInfoKey
//: ### When your representations differ across endpoints
//: Contextual information may be provided during encoding and decoding using `userInfo` property of Decoder and Encoder.
import Foundation

enum CustomCodingOptionsError: Error {
    case optionsNotProvided
}

struct CustomCodingOptions {
    enum ApiVersion { case v1, v2 }

    let apiVersion: ApiVersion
    let oldDateFormatter: DateFormatter

    static let key = CodingUserInfoKey(rawValue: "com.intive.customercodingoptions")!
}

struct Retailer {
    let name: String
    let website: URL?
    let founded: Date?

    enum CodingKeys: String, CodingKey {
        case oldName = "retailer_name"
        case name
        case website
        case founded
    }
}

extension Retailer: Encodable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        guard let options = encoder.userInfo[CustomCodingOptions.key] as? CustomCodingOptions else {
            throw CustomCodingOptionsError.optionsNotProvided
        }

        switch options.apiVersion {
        case .v1:
            try container.encode(name, forKey: .oldName)

            guard let founded = founded else { break }
            let oldDate = options.oldDateFormatter.string(from: founded)
            try container.encode(oldDate, forKey: .founded)
        case .v2:
            try container.encode(name, forKey: .name)
            try container.encode(founded, forKey: .founded)
        }

        try container.encode(website, forKey: .website)
    }
}

extension Retailer: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        guard let options = decoder.userInfo[CustomCodingOptions.key] as? CustomCodingOptions else {
            throw CustomCodingOptionsError.optionsNotProvided
        }

        switch options.apiVersion {
        case .v1:
            name = try container.decode(String.self, forKey: .oldName)
            let oldFounded = try container.decode(String.self, forKey: .founded)
            founded = options.oldDateFormatter.date(from: oldFounded)
        case .v2:
            name = try container.decode(String.self, forKey: .name)
            founded = try container.decode(Date.self, forKey: .founded)
        }

        website = try container.decode(URL.self, forKey: .website)
    }
}


let encoder = JSONEncoder()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM-dd-yyyy"
let options = CustomCodingOptions(apiVersion: .v1, oldDateFormatter: dateFormatter)

encoder.userInfo = [CustomCodingOptions.key: options]

/*:
 - - -

 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)
 */
/*:
 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)

 - - -
 */
//: # Decoding nested data using intermediate type
//: ### Ignore structure and data in JSON that you don’t need in your code
import Foundation
//: Target data structure:
struct Restaurant {
    let name: String
    let cheapestDish: Dish?

    struct Dish: Decodable {
        let name: String
        let price: Float
    }
}

//: Actual structure:
let restaurantsJSON = """
[
  {
    "name": "Healthy Place #1",
    "menuSections": [
      {
        "name": "Appetizers",
        "dishes": [
          {
            "name": "Topas",
            "price": 10
          }
        ]
      },
      {
        "name": "Regular Meals",
        "dishes": [
          {
            "name": "Chickpeas Curry",
            "price": 20
          },
          {
            "name": "Jumbo Large Pizza",
            "price": 40
          }
        ]
      },
      {
        "name": "Desserts",
        "dishes": [
          {
            "name": "🍧 Ice Cream 🍦",
            "price": 9.90
          },
          {
            "name": "Cake",
            "price": 15
          }
        ]
      }
    ]
  }
]
""".data(using: .utf8)!

//: - Note: To decode actual data to desired structure you can use intermediate decodable type that mirrors actual data structure:
struct RestaurantMediator: Decodable {
    let name: String
    let menuSections: [MenuSection]

    struct MenuSection: Decodable {
        let name: String
        let dishes: [Restaurant.Dish]
    }
}

//: Than create your type using intermediate type
extension Restaurant {
    init(using mediator: RestaurantMediator) {
        name = mediator.name

        let allDishes = mediator.menuSections.flatMap { $0.dishes }
        cheapestDish = allDishes.min { $0.price < $1.price }
    }
}

let decoder = JSONDecoder()
let restaurantsMediator = try decoder.decode([RestaurantMediator].self, from: restaurantsJSON)

let restaurants = restaurantsMediator.map { Restaurant(using: $0) }
dump(restaurants)

//: ---
//: ## Different way of accesing nested data
struct OtherRestaurant: Decodable {
    let name: String
    let cheapestDish: Dish?

    struct Dish: Decodable {
        let name: String
        let price: Float
    }
}

extension OtherRestaurant {

    enum CodingKeys: CodingKey {
        case name, menuSections
    }

    enum SectionsCodingKeys: CodingKey {
        case name, dishes
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        name = try container.decode(String.self, forKey: .name)

        var sectionsArray = try container.nestedUnkeyedContainer(forKey: .menuSections)

        var dishes = [Dish]()

/*:
- Note:
Access all elements in the container using while loop and unkeyed container's `isAtEnd` property.\
To access current decoding index of the container use `currentIndex` property.\
Number of elements can be accessed through `count` property.
 */
        while sectionsArray.isAtEnd == false {
            let sectionContainer = try sectionsArray.nestedContainer(keyedBy: SectionsCodingKeys.self)
            var dishesForSectionArray = try sectionContainer.nestedUnkeyedContainer(forKey: .dishes)

            while dishesForSectionArray.isAtEnd == false {
                let dish = try dishesForSectionArray.decode(Dish.self)
                dishes.append(dish)
            }
        }

//: - Note: Similarly for keyed containers, keys can be accessed using `allKeys` property.

        cheapestDish = dishes.min { $0.price < $1.price }
    }
}

let otherRestaurant = try? decoder.decode([OtherRestaurant].self, from: restaurantsJSON)

/*:
 - - -

 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)
 */



/*:
 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)

 - - -
 */
//: # Dynamic Coding Keys
//: ### Access data at different depths
//: Encoding to and decoding from structure that contains keys with names unknown until runtime is possible by implementing CodingKeys as a `struct` conforming to `CodingKey` protocol.
import Foundation

let bandWithRolesData =
"""
{
    "guitarist": {
        "name": "Jimi",
        "instrumentName": "Strat 🎸",
        "isInstrumentElectric": true
    },
    "drummer": {
        "name": "Dave",
        "instrumentName": "Drums 🥁",
        "isInstrumentElectric": false
    },
    "saxophonist": {
        "name": "John",
        "instrumentName": "Sax 🎷",
        "isInstrumentElectric": false
    }
}
""".data(using: .utf8)!

struct RockBand {

    struct RockStar {
        let name: String
        let role: String
        let instrument: Instrument

        struct Instrument {
            let name: String
            let isElectric: Bool?
        }
    }

    let rockStars: [RockStar]
}

extension RockBand {

    struct DynamicCodingKeys: CodingKey {
//: For named collection (e.g. a string-keyed dictionary):
        var stringValue: String

//: For integer-indexed collection (e.g. an int-keyed dictionary):
        var intValue: Int? { return nil }

        init(stringValue: String) {
            self.stringValue = stringValue
        }

        init?(intValue: Int) { return nil }

        static let name = DynamicCodingKeys(stringValue: "name")
        static let instrumentName = DynamicCodingKeys(stringValue: "instrumentName")
        static let isInstrumentElectric = DynamicCodingKeys(stringValue: "isInstrumentElectric")
    }
}

extension RockBand: Decodable {

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: DynamicCodingKeys.self)

        var rockStars = [RockStar]()
        for key in container.allKeys {
            let rockStarContainer = try container.nestedContainer(keyedBy: DynamicCodingKeys.self, forKey: key)
            let name = try rockStarContainer.decode(String.self, forKey: .name)
            let instrumentName = try rockStarContainer.decode(String.self, forKey: .instrumentName)
            let isInstrumentElectric = try rockStarContainer.decode(Bool.self, forKey: .isInstrumentElectric)

            rockStars.append(RockStar(name: name, role: key.stringValue, instrument: RockStar.Instrument(name: instrumentName, isElectric: isInstrumentElectric)))
        }

        self.init(rockStars: rockStars)
    }
}

extension RockBand: Encodable {

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: DynamicCodingKeys.self)

        for rockStar in rockStars {
            let nameKey = DynamicCodingKeys(stringValue: rockStar.role)
            var rockStarContainer = container.nestedContainer(keyedBy: DynamicCodingKeys.self, forKey: nameKey)

            try rockStarContainer.encode(rockStar.name, forKey: .name)
            try rockStarContainer.encode(rockStar.instrument.name, forKey: .instrumentName)
            try rockStarContainer.encode(rockStar.instrument.isElectric, forKey: .isInstrumentElectric)
        }
    }
}

let decoder = JSONDecoder()
let bandWithRoles = try decoder.decode(RockBand.self, from: bandWithRolesData)
dump(bandWithRoles)

/*:
 - - -

 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)
 */

/*:
 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)

 - - -
 */
//: # Encoder and Decoder customization
import Foundation

let encoder = JSONEncoder()
let decoder = JSONDecoder()

//: ### Output formatting
//: Human-readable JSON with indented output:
encoder.outputFormatting = .prettyPrinted

//: Keys sorted in lexicographic (alphabetic) order:
encoder.outputFormatting = .sortedKeys

//: ### Keys encoding/decoding strategy
//: To customize strategy for keys conversion you can just use encoder/decoder's `keyEncodingStrategy`/`keyDecodingStrategy`. For example to convert between snake case (snake_case) and camel case (camelCase) you can simply use  `convertToSnakeCase` / `convertFromSnakeCase` strategy

encoder.keyEncodingStrategy = .convertToSnakeCase
decoder.keyDecodingStrategy = .convertFromSnakeCase

//: For more advanced conversions use `custom` strategy, which enables to implement custom conversion closure:

struct AnyKey: CodingKey {
    var stringValue: String
    var intValue: Int?

    init(stringValue: String) {
        self.stringValue = stringValue
        self.intValue = nil
    }

    init(intValue: Int) {
        self.stringValue = String(intValue)
        self.intValue = intValue
    }
}

encoder.keyEncodingStrategy = .custom { keys -> CodingKey in
    let lastComponent = keys.last?.stringValue.uppercased()
    return AnyKey(stringValue: lastComponent ?? "")
}

//: ### Encoding (and decoding) Date
//: Formatting used by the Date type:
encoder.dateEncodingStrategy = .deferredToDate
/*:
    {
        date = "544477449.093259";
    }
*/

encoder.dateEncodingStrategy = .iso8601
/*:
    {
        date = "2018-04-03T19:46:52Z";
    }
*/

encoder.dateEncodingStrategy = .millisecondsSince1970
encoder.dateEncodingStrategy = .secondsSince1970
/*:
    {
        date = "1522784861.46883";
    }
 */

//: Use custom DateFormatter:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-MMM-yyyy"

encoder.dateEncodingStrategy = .formatted(dateFormatter)

struct 🗓: Codable {
    let date: Date
}

let today = 🗓(date: Date(timeIntervalSinceNow: 0))
let todayData = try encoder.encode(today)
let todayJSON = try JSONSerialization.jsonObject(with: todayData)

/*:
 * Callout(Update for Swift 4.1):
 Swift 4.1 adds new properties to JSONDecoder and JSONEncoder: `keyDecodingStrategy` and `keyEncodingStrategy` respectively.\
 Those properties allow convertion between snake case (`snake_case`) and camel case (`camelCase`) without writing custom implemantaion of CodingKeys.
 */
//: ### Encoding (and decoding) exceptional numbers (like infinity ∞)
//: Throw error:
encoder.nonConformingFloatEncodingStrategy = .throw

//: Convert to string:
encoder.nonConformingFloatEncodingStrategy = .convertToString(positiveInfinity: "+inf+", negativeInfinity: "-inf-", nan: "NaN")

struct allNumbers: Codable {
    let count: Float
}

let infinity = allNumbers(count: .infinity)
let infinityData = try encoder.encode(infinity)
let infinityJSON = try JSONSerialization.jsonObject(with: infinityData)

//: # Property List Encoder and Decoder
let plistEncoder = PropertyListEncoder()

plistEncoder.outputFormat = .openStep
plistEncoder.outputFormat = .binary
plistEncoder.outputFormat = .xml

let todayPlistData = try plistEncoder.encode(today)
try PropertyListSerialization.propertyList(from: todayPlistData, options: .mutableContainers, format: nil)

let plistDecoder = PropertyListDecoder()

if let myCalendarFilePath = Bundle.main.path(forResource: "MyCalendar", ofType: "plist"),
    let myCalendarData = FileManager.default.contents(atPath: myCalendarFilePath) {

    try plistDecoder.decode(🗓.self, from: myCalendarData)
}

/*:
 - - -

 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)
 */
/*:
 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)

 - - -
 */
//: # Error Handling
//: ## Decoding Errors

import Foundation

struct Vegetable: Codable {
    let id: UUID
    let name: String?
    let waterContent: Float

    init(id: UUID, name: String?, waterContent: Float) {
        self.id = id
        self.name = name
        self.waterContent = waterContent
    }
}

//: ### Catching errors during decoding:

extension Vegetable {
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        id = try container.decode(UUID.self, forKey: .id)
        name = try container.decode(String.self, forKey: .name)
        waterContent = try container.decode(Float.self, forKey: .waterContent)
        guard case 0...100 = waterContent else {
            let context = DecodingError.Context(codingPath: container.codingPath + [CodingKeys.waterContent],
                                                debugDescription: "Wrong value: waterContent does not lie within the range of 0 - 100")
            throw DecodingError.dataCorrupted(context)
        }
    }
}

let decoder = JSONDecoder()

let wrongValue = """
{
  "id": "E621E1F8-C36C-495A-93FC-0C247A3E6E5F",
  "name": "Banana",
  "waterContent": 101
}
""".data(using: .utf8)!

do {
    _ = try decoder.decode(Vegetable.self, from: wrongValue)
} catch DecodingError.dataCorrupted(let context) {
    print("‼️ \(context.debugDescription)\n")
} catch {
    print(error.localizedDescription)
}

//: ### Corrupted Data
let corruptedData = """
👾👾👾
""".data(using: .utf8)!

do {
    _ = try decoder.decode(Vegetable.self, from: corruptedData)
} catch DecodingError.dataCorrupted(let context) {
    print("‼️ \(context.debugDescription)")
    print(context.underlyingError ?? "Underlying error unknown", terminator: "\n\n")
} catch {
    print(error.localizedDescription)
}

//: ### Key not found
let keyNotFoundData = """
{
  "id": "E621E1F8-C36C-495A-93FC-0C247A3E6E5F"
}
""".data(using: .utf8)!

do {
    _ = try decoder.decode(Vegetable.self, from: keyNotFoundData)
} catch DecodingError.keyNotFound(let key, let context) {
    print("‼️ Missing key: \(key)")
    print("Debug description: \(context.debugDescription)\n")
} catch {
    print(error.localizedDescription)
}

//: ### Type mismatch
let wrongTypeData = """
{
  "id": 9,
  "name": "Banana",
  "waterContent": 74.9
}
""".data(using: .utf8)!

do {
    _ = try decoder.decode(Vegetable.self, from: wrongTypeData)
} catch DecodingError.typeMismatch(let type, let context) {
    print("‼️ Type mismatch: expected \(type)")
    print("Debug description: \(context.debugDescription)\n")
} catch {
    print(error.localizedDescription)
}

//: ### Value not found

let valueNotFoundData = """
{
  "id": null,
  "name": "Banana",
  "waterContent": 74.9
}
""".data(using: .utf8)!

do {
    _ = try decoder.decode(Vegetable.self, from: valueNotFoundData)
} catch DecodingError.valueNotFound(let type, let context) {
    print("‼️ Value of type \(type) not found")
    print("Debug description: \(context.debugDescription)\n")
} catch {
    print(error.localizedDescription)
}

//: ## Encoding Error
//: ### Invalid Value

let eggplant = Vegetable(id: UUID(), name: "Eggplant 🍆", waterContent: .infinity)
let encoder = JSONEncoder()

do {
    _ = try JSONEncoder().encode(eggplant)
} catch EncodingError.invalidValue(let value, let context) {
    print("‼️ Invalid value: \(value)")
    print("Debug description: \(context.debugDescription)\n")
} catch {
    print(error.localizedDescription)
}

/*:
 - - -

 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)
 */
/*:
 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)

 - - -
 */
//: # Manual Decoding
//: ### Customizing default implementation
//: ---
//: ## How to decode nested objects from flat object
import Foundation

struct RockStar {
    let name: String
    let instrument: Instrument
}

struct Instrument {
    let name: String
    let isElectric: Bool?
}

extension RockStar: Decodable {
/*:
- Note: `Decodable` protocol has one requirement:\
`init(from decoder: Decoder)`
*/
//: **First:** specify coding keys, typically with enumerator:
    enum CodingKeys: String, CodingKey {
        case name
        case instrumentName
        case isInstrumentElectric
    }
//: **Second:** customize initializer:
    init(from decoder: Decoder) throws {
//: **Third:** create container with stored data from decoder:
        let container = try decoder.container(keyedBy: CodingKeys.self)

//: **Than:** initialize properties with decoded values of the given type for the given key:
        name = try container.decode(String.self, forKey: .name)

        let instrumentName = try container.decode(String.self, forKey: .instrumentName)
        let isInstrumentElectric = try container.decodeIfPresent(Bool.self, forKey: .isInstrumentElectric)
        instrument = Instrument(name: instrumentName, isElectric: isInstrumentElectric)
    }
}

//: ---
//: ## Examples

let jimiData =
"""
{
  "name": "Jimi",
  "instrumentName": "Strat 🎸",
  "isInstrumentElectric": true
}
""".data(using: .utf8)!

let decoder = JSONDecoder()

let jimi = try? decoder.decode(RockStar.self, from: jimiData)

//: ---

let bandData =
"""
[
  {
    "name": "Jimi",
    "instrumentName": "Strat 🎸",
    "isInstrumentElectric": true
  },
  {
    "name": "Dave",
    "instrumentName": "Drums 🥁",
  },
  {
    "name": "John",
    "instrumentName": "Sax 🎷",
    "isInstrumentElectric": false
  }
]
""".data(using: .utf8)!

let band = try? decoder.decode([RockStar].self, from: bandData)

//: ---

let bandWithRolesData =
"""
[
  {
    "guitarist": {
      "name": "Jimi",
      "instrumentName": "Strat 🎸",
      "isInstrumentElectric": true
    }
  },
  {
    "drummer": {
      "name": "Dave",
      "instrumentName": "Drums 🥁",
      "isInstrumentElectric": false
    }
  },
  {
    "cat": {
      "name": "John",
      "instrumentName": "Sax 🎷",
      "isInstrumentElectric": false
    }
  }
]
""".data(using: .utf8)!

let bandWithRoles = try? decoder.decode([[String:RockStar]].self, from: bandWithRolesData)

print("Here comes the band of \(bandWithRoles?.count ?? 0)!")
dump(bandWithRoles)

//: ---
//: ## How to decode flat object from nested objects

struct Guitar {
    let name: String
    let numberOfStrings: Int
    let isElectric: Bool?
}

let ukuleleData =
"""
{
  "name": "Ukulele",
  "info": {
    "numberOfStrings": 4,
    "isElectric": false
  }
}
""".data(using: .utf8)!

extension Guitar: Decodable {

    enum CodingKeys: String, CodingKey {
        case name
        case info
    }

    enum InfoCodingKeys: CodingKey {
        case numberOfStrings
        case isElectric
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)

//: - Note: Create container for nested objects, decode their properties and assign them:
        let infoContainer = try container.nestedContainer(keyedBy: InfoCodingKeys.self, forKey: .info)
        numberOfStrings = try infoContainer.decode(Int.self, forKey: .numberOfStrings)
        isElectric = try infoContainer.decodeIfPresent(Bool.self, forKey: .isElectric)
    }
}

let myNewUkulele = try decoder.decode(Guitar.self, from: ukuleleData)

/*:
 - - -

 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)
 */


/*:
 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)

 - - -
 */
//: # Manual Encoding
//: ### Customizing default implementation
//: ---
//: ## How to manually encode nested objects to flat object
import Foundation
import CoreGraphics

struct Pixel {
    enum PixelState: String { case up, down }

    let state: PixelState
    let coordinates: CGPoint
}

extension Pixel: Encodable {
/*:
- Note:
`Encodable` protocol requirement is to adopt one method:\
`public func encode(to encoder: Encoder)`
 */
//: **First:** specify coding keys, typically with enumerator:
    enum CodingKeys: String, CodingKey {
        case state
        case x
        case y
    }

//: **Second:** customize `encode(to:)` method:
    func encode(to encoder: Encoder) throws {
/*:
**Third:** create container. Values will be written to the container, so it must be mutable (`var`).

- Note: Container types:\
_1. keyed container (dictionary): for holding multiple values provided by the key type\
_2. unkeyed container (array): for holding multiple unkeyed values\
_3. single value container: for holding a single primitive value
*/
        var container = encoder.container(keyedBy: CodingKeys.self)
//: **Than:** encode values for the given keys.
        try container.encode(state.rawValue, forKey: .state)
        try container.encode(coordinates.x, forKey: .x)
        try container.encode(coordinates.y, forKey: .y)
    }
}

let pixel = Pixel(state: .up, coordinates: CGPoint(x: 50, y: 100))

let encoder = JSONEncoder()

let pixelData = try encoder.encode(pixel)
let pixelJSON = try JSONSerialization.jsonObject(with: pixelData)

//: ---
//: ## How to encode flat object to nested objects

struct Guitar {
    let name: String
    let numberOfStrings: Int
    let isElectric: Bool?
}

let strat = Guitar(name: "Strat 🎸", numberOfStrings: 6, isElectric: true)

/*:
 Using default Encodable implementation, `Guitar` structure would be encoded to following form:

    {
    name = "Strat \Ud83c\Udfb8";
    numberOfStrings = 6;
    isElectric = 1;
    }

 To wrap some properties into nested collection use container's **nested containers**, which can be:

 - Note: Nested Container types:\
 _1. keyed (dictionary): returned by container's `nestedContainer(keyedBy:forKey:)`\
 _2. unkeyed (array): returned by container's `nestedUnkeyedContainer(forKey:)`
 */
extension Guitar: Encodable {

    enum CodingKeys: String, CodingKey {
        case name
        case info
    }

    enum InfoCodingKeys: CodingKey {
        case numberOfStrings
        case isElectric
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)

        var infoContainer = container.nestedContainer(keyedBy: InfoCodingKeys.self, forKey: .info)
        try infoContainer.encode(numberOfStrings, forKey: .numberOfStrings)
        try infoContainer.encode(isElectric, forKey: .isElectric)

/*:
- Note:
When automatically encoding optional type, `encodeIfPresent` method is used by default.\
Using manual encoding `encode` method can be used explicitly, so the key won't be missing in the encoded object in case it's nil.
 */
    }
}

let data = try encoder.encode(strat)
let JSON = try JSONSerialization.jsonObject(with: data)

/*:
 - - -

 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)
 */

/*:
 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)

- - -

 # Overview

 Encoding and decoding is about the conversion between native and custom Swift data structures and archived formats, especially JSON.\
 Codable protocols allows easy convertion between loosely typed JSON and strong Swift type.

 _Codable_ - A type that can convert itself into and out of an external representation

 - Note: Codable by definition is alias to two protocols:
 \
 `typealias Codable = Decodable & Encodable`


 - Important:
 `Encodable` - A type that can encode itself **to** an external representation
 \
 `Decodable` - A type that can decode itself **from** an external representation

 ---
 Many standard and non standard types already adopt Codable protocols, including: `String`, `Int`, `Double`, `Decimal`, `Bool`, `Date`, `DateInterval`, `DateComponents`, `Calendar`, `TimeZone`, `Data`, `URL`, `PersonNameComponents`.

 Any type whose all properties are codable automatically conforms to Codable just by declaring that conformance:
 */
import Foundation

struct Retailer: Codable {
    let name: String
    let website: URL
    let establishedIn: Date
}
/*:
 `Retailer` now supports the `Codable` methods `init(from:)` and `encode(to:)` and can be serialized to and from any data formats provided by custom encoders and decoders (eg. JSONEncoder, PropertyListEncoder).
 */
struct Vegetable: Codable {
    let identifier: Int
    let name: String?
    let isDelicious: Bool
    let imagesURLs: [URL]
    let nutrition: [String: Float]
    let availability: [Retailer]
}
/*:
 - - -

 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)
 */
/*:
 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)

 - - -
 */
//: # Real World Example
//: ### Not so pretty now
import Foundation

let artistData = """
{
  "items": {
    "item": [
      {
        "id": "768g8g8",
        "name": "Sarsa",
        "bio": "Born in 1989 in Slupsk",
        "images": {
          "image": [
            {
              "width": "320",
              "height": "400",
              "format": "png",
              "imgUrl": "http://imageprovider.com/srsrs1.jpg"
            },
            {
              "width": "300",
              "height": "400",
              "format": "jpg",
              "imgUrl": "http://imageprovider.com/srsrs2.jpg"
            }
          ]
        }
      },
      {
        "id": "bvhjvy68",
        "name": "Jane Doe",
        "bio": "Jane Doe has done some things",
        "images": {
          "image": []
        }
      }
    ]
  }
}
""".data(using: .utf8)!

public struct ArtistsFeed {

    public var artists: [DetailedArtist]

    public init(from response: ArtistsFeedResponse) {
        artists = response.artists.map {
            DetailedArtist(artistId: $0.artistId,
                           name: $0.name, bio: $0.bio, artistImages: $0.artistImages)
        }
    }
}

public struct ArtistsFeedResponse: Decodable {

    let artists: [DetailedArtist]
}

extension ArtistsFeedResponse {

    enum CodingKeys: String, CodingKey {
        case items
    }

    enum AdditionalItemKeys: String, CodingKey {
        case item
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let itemContainer = try container.nestedContainer(keyedBy: AdditionalItemKeys.self, forKey: .items)
        var artistsContainer = try itemContainer.nestedUnkeyedContainer(forKey: .item)

        var artists = [DetailedArtist]()

        while artistsContainer.isAtEnd == false {
            let artist = try artistsContainer.decode(DetailedArtist.self)
            artists.append(artist)
        }

        self.init(artists: artists)
    }
}

public struct DetailedArtist: Decodable {

    public let artistId: String
    public var name: String
    public let bio: String?
    public let artistImages: [DetailedImage]
}

extension DetailedArtist {

    enum CodingKeys: String, CodingKey {
        case artistId = "id"
        case name
        case bio
        case artistImages = "images"
    }

    enum AdditionalImageKeys: String, CodingKey {
        case image
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let artistId = try container.decode(String.self, forKey: .artistId)
        let name = try container.decode(String.self, forKey: .name)
        let bio = try container.decodeIfPresent(String.self, forKey: .bio)

        let imagesContainer = try container.nestedContainer(keyedBy: AdditionalImageKeys.self, forKey: .artistImages)
        let images = try imagesContainer.decode([DetailedImage].self, forKey: .image)

        self.init(artistId: artistId, name: name, bio: bio, artistImages: images)
    }
}

public struct DetailedImage: Decodable {

    public let width: Int?
    public let height: Int?
    public let format: String
    public let url: URL?
}

extension DetailedImage {

    enum CodingKeys: String, CodingKey {
        case width
        case height
        case format
        case url = "imgUrl"
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        let width = try container.decode(String.self, forKey: .width)
        let height = try container.decode(String.self, forKey: .height)
        let format = try container.decode(String.self, forKey: .format)
        let url = try container.decode(URL.self, forKey: .url)

        self.init(width: Int(width), height: Int(height), format: format, url: url)
    }
}

let decoder = JSONDecoder()

let artistFeedResponse = try decoder.decode(ArtistsFeedResponse.self, from: artistData)
let feed = ArtistsFeed(from: artistFeedResponse)

dump(feed)

/*:
 - - -

 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)
 */
/*:
 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)

 - - -

 * Callout(Resources): For more information go to:
 * [Encoding and Decoding Custom Types](https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types)
 * [What's New in Foundation, WWDC 2017, 23 minute onwards](https://developer.apple.com/videos/play/wwdc2017/212/)
 * [Using JSON with Custom Types (Sample Code)](https://developer.apple.com/documentation/foundation/archives_and_serialization/using_json_with_custom_types)
 * [Ultimate Guide to JSON Parsing with Swift 4](https://benscheirman.com/2017/06/swift-json/)

 - - -

 [[< Previous]](@previous)  [[Table of Contents]](Start)  [[Next >]](@next)
 */
/*:
 [[Table of Contents]](Start)  [[Next >]](@next)

 - - -

 # Codable
 ![Enigma](enigma.jpg)

 Codable playground by Filip Zieliński\
 for Intive iOS Guild presentation 5 IV 2018

 ## Table of Contents
 1. [Start](Start)
 2. [Overview](Overview)
 3. [Automatic Encoding](AutomaticEncoding)
 4. [Automatic Decoding](AutomaticDecoding)
 5. [Manual Encoding](ManualEncoding)
 6. [Manual Decoding](ManualDecoding)
 7. [Inheritance](CodableClasses)
 8. [Decoding nested data using intermediate type](DecodeWithIntermediateType)
 9. [Real World Example](RealWorldExample)
 10. [Dynamic Coding Keys](DynamicCodingKey)
 11. [Encoder and Decoder customization](EncoderDecoderCustomization)
 12. [CodingUserInfoKey](CodingUserInfoKey)
 13. [Error Handling](ErrorHandling)
 14. [Resources](Resources)

[Title image](https://www.flickr.com/photos/_dchris/20235659265) by [_dChris](https://www.flickr.com/photos/_dchris/) is under [CC license](https://creativecommons.org/licenses/by/2.0/).

 - - -

 [[Table of Contents]](Start)  [[Next >]](@next)
 */