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
- Start with automatic synthesis when possible
- Use CodingKeys for simple key mapping
- Implement custom encoding/decoding only when necessary
- Handle errors appropriately
- 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
Related Topics
- 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

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)
*/