
Codable: Tips and Examples
- Transfer
I would like to share with you some tips and tricks that I used on this example.
Download the Swift Playground with all the code from this article:

Codable is introduced in Swift 4 with the goal of replacing the old NSCoding API. Unlike NSCoding , Codable has first-class JSON support, making it a promising option for using the JSON API.
Codable is great as NSCoding . If you need to encode or decode some local data that you need to control fully, you can take advantage of automatic encoding and decoding .
In the real world, everything becomes quite complicated and fast. Trying to build a fail-safe system that will manage JSON and model all product requirements is a problem.
One of the main drawbacks of Codable is that as soon as you need custom decoding logic - even for one key - you have to provide everything: manually define all the encoding keys and implement init (from decoder: Decoder) throws completely manually . This is not perfect. But this is at least as good (or bad) as using third-party JSON libraries in Swift. The presence of such in the built library is already a victory.
Therefore, if you want to start using Codable in your application (and you are already familiar with all its main features), here are a few tips and examples that may turn out to be useful:
Suppose you want to download and display a collection of posts in your application. Each Post has id (required), title (required) and subtitle (optional).
The Post class models the requirements well. It already accepts the Decodable protocol , so we are ready to decode some data:
As expected, we got a .keyNotFound error , because the second post object does not have a title.
When the data does not match the expected format (for example, this may be the result of misunderstanding, regression, or unexpected user input), the system should automatically report an error to enable developers to fix it. Swift provides a detailed error report in the form of a DecodingError at any time when decoding fails, which is extremely useful.
In most cases, no one wants one damaged post to stop you from displaying a whole page of other completely correct posts. To prevent this, I use the special type Safewhich allows me to safely decode the object. If it detects an error during decoding, it will go into safe error detection mode and send a report:
Now, when I decode an array, I can indicate that I do not want to stop decoding in case of one damaged element:
Keep in mind that decode decode ([Safe] .self, from: ... throws an error if the data does not contain an array. In the general case, such errors should be caught at a higher level. The general API contract is to always return an empty array if there are no elements to return.
In the previous example, I used the special identifier Id. Type Id receives parameters using the general parameter Entity, which is not actually used by Id itself, but is used by the compiler when comparing different types of Id. Thus, the compiler ensures that I cannot accidentally pass Id where Id is expected
.
I also used phantom types for security when using types, in the client API article in Swift .
The Id type itself is very simple, it's just a wrapper on top of the original String :
Adding Codable to it is definitely a daunting task. This requires a special type of SingleValueEncodingContainer :
A container that can support storing and directly encoding a single unblocked value.
As you can see from the code above, Id also has a special rule that prevents it from being initialized from an empty String.
Swift has excellent support for decoding (and encoding) enumerations. In many cases, all you have to do is simply declare a Decodable match, which is automatically synthesized by the compiler (the raw type of the enumeration must be either String or Int).
Suppose you create a system that displays all your devices on a map. The device has a location (required) and a system (required) that it launches.
Now there is a question. What if more systems are added in the future? The solution to the product may be to still display these devices, but somehow indicate that the system is “unknown”. How should you simulate this in an application?
By default, Swift will throw a .dataCorrupted error if an unknown enum value is encountered:
How can a system be modeled and decoded in a secure manner? One way is to make the system property optional, which will mean "Unknown". And the easiest way to safely decode the system is to implement a custom init (from decoder: Decoder) throws initializer:
Keep in mind that this version simply ignores all possible problems with the value of system. This means that even “corrupted” data (for example, missing key system, number 123, null, empty object {} - depending on which API contract) is decoded as nil (“Unknown”). A more accurate way of saying is "decode unknown strings as nil":
In the previous example, we had to implement a custom initializer init (from decoder: Decoder) throws, which turned out to be quite a lot of code. Fortunately, there are several ways to do this more concisely.
One option is to get rid of explicit type parameters:
Let's go back to our Post example and extend it with the webURL property (optional). If we try to decode the data published below, we get a .dataCorrupted error along with the main error:
Download the Swift Playground with all the code from this article:

Codable is introduced in Swift 4 with the goal of replacing the old NSCoding API. Unlike NSCoding , Codable has first-class JSON support, making it a promising option for using the JSON API.
Codable is great as NSCoding . If you need to encode or decode some local data that you need to control fully, you can take advantage of automatic encoding and decoding .
In the real world, everything becomes quite complicated and fast. Trying to build a fail-safe system that will manage JSON and model all product requirements is a problem.
One of the main drawbacks of Codable is that as soon as you need custom decoding logic - even for one key - you have to provide everything: manually define all the encoding keys and implement init (from decoder: Decoder) throws completely manually . This is not perfect. But this is at least as good (or bad) as using third-party JSON libraries in Swift. The presence of such in the built library is already a victory.
Therefore, if you want to start using Codable in your application (and you are already familiar with all its main features), here are a few tips and examples that may turn out to be useful:
- Safe decoding of arrays
- Identifier Type and Single Value Container
- Secure Decoding of Transfers
- Few manual decoding
- Getting rid of certain types of parameters
- Using a separate decoding scheme
- Encoding Patch Options
Safe decoding of arrays
Suppose you want to download and display a collection of posts in your application. Each Post has id (required), title (required) and subtitle (optional).
final class Post: Decodable {
let id: Id // More about this type later.
let title: String
let subtitle: String?
}
The Post class models the requirements well. It already accepts the Decodable protocol , so we are ready to decode some data:
[
{
"id": "pos_1",
"title": "Codable: Tips and Tricks"
},
{
"id": "pos_2"
}
]
do {
let posts = try JSONDecoder().decode([Post].self, from: json.data(using: .utf8)!)
} catch {
print(error)
//prints "No value associated with key title (\"title\")."
}
As expected, we got a .keyNotFound error , because the second post object does not have a title.
When the data does not match the expected format (for example, this may be the result of misunderstanding, regression, or unexpected user input), the system should automatically report an error to enable developers to fix it. Swift provides a detailed error report in the form of a DecodingError at any time when decoding fails, which is extremely useful.
In most cases, no one wants one damaged post to stop you from displaying a whole page of other completely correct posts. To prevent this, I use the special type Safe
public struct Safe: Decodable {
public let value: Base?
public init(from decoder: Decoder) throws {
do {
let container = try decoder.singleValueContainer()
self.value = try container.decode(Base.self)
} catch {
assertionFailure("ERROR: \(error)")
// TODO: automatically send a report about a corrupted data
self.value = nil
}
}
}
Now, when I decode an array, I can indicate that I do not want to stop decoding in case of one damaged element:
do {
let posts = try JSONDecoder().decode([Safe].self, from: json.data(using: .utf8)!)
print(posts[0].value!.title) // prints "Codable: Tips and Tricks"
print(posts[1].value) // prints "nil"
} catch {
print(error)
}
Keep in mind that decode decode ([Safe
Identifier Type and Single Value Container
In the previous example, I used the special identifier Id
I also used phantom types for security when using types, in the client API article in Swift .
The Id type itself is very simple, it's just a wrapper on top of the original String :
public struct Id: Hashable {
public let raw: String
public init(_ raw: String) {
self.raw = raw
}
public var hashValue: Int {
return raw.hashValue
}
public static func ==(lhs: Id, rhs: Id) -> Bool {
return lhs.raw == rhs.raw
}
}
Adding Codable to it is definitely a daunting task. This requires a special type of SingleValueEncodingContainer :
A container that can support storing and directly encoding a single unblocked value.
extension Id: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let raw = try container.decode(String.self)
if raw.isEmpty {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Cannot initialize Id from an empty string"
)
}
self.init(raw)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(raw)
}
}
As you can see from the code above, Id also has a special rule that prevents it from being initialized from an empty String.
Secure Decoding of Transfers
Swift has excellent support for decoding (and encoding) enumerations. In many cases, all you have to do is simply declare a Decodable match, which is automatically synthesized by the compiler (the raw type of the enumeration must be either String or Int).
Suppose you create a system that displays all your devices on a map. The device has a location (required) and a system (required) that it launches.
enum System: String, Decodable {
case ios, macos, tvos, watchos
}
struct Location: Decodable {
let latitude: Double
let longitude: Double
}
final class Device: Decodable {
let location: Location
let system: System
}
Now there is a question. What if more systems are added in the future? The solution to the product may be to still display these devices, but somehow indicate that the system is “unknown”. How should you simulate this in an application?
By default, Swift will throw a .dataCorrupted error if an unknown enum value is encountered:
{
"location": {
"latitude": 37.3317,
"longitude": 122.0302
},
"system": "caros"
}
do {
let device = try JSONDecoder().decode(Device.self, from: json.data(using: .utf8)!)
} catch {
print(error)
// Prints "Cannot initialize System from invalid String value caros"
}
How can a system be modeled and decoded in a secure manner? One way is to make the system property optional, which will mean "Unknown". And the easiest way to safely decode the system is to implement a custom init (from decoder: Decoder) throws initializer:
final class Device: Decodable {
let location: Location
let system: System?
init(from decoder: Decoder) throws {
let map = try decoder.container(keyedBy: CodingKeys.self)
self.location = try map.decode(Location.self, forKey: .location)
self.system = try? map.decode(System.self, forKey: .system)
}
private enum CodingKeys: CodingKey {
case location
case system
}
}
Keep in mind that this version simply ignores all possible problems with the value of system. This means that even “corrupted” data (for example, missing key system, number 123, null, empty object {} - depending on which API contract) is decoded as nil (“Unknown”). A more accurate way of saying is "decode unknown strings as nil":
self.system = System(rawValue: try map.decode(String.self, forKey: .system))
Few manual decoding
In the previous example, we had to implement a custom initializer init (from decoder: Decoder) throws, which turned out to be quite a lot of code. Fortunately, there are several ways to do this more concisely.
Getting rid of certain types of parameters
One option is to get rid of explicit type parameters:
extension KeyedDecodingContainer {
public func decode(_ key: Key, as type: T.Type = T.self) throws -> T {
return try self.decode(T.self, forKey: key)
}
public func decodeIfPresent(_ key: KeyedDecodingContainer.Key) throws -> T? {
return try decodeIfPresent(T.self, forKey: key)
}
}
Let's go back to our Post example and extend it with the webURL property (optional). If we try to decode the data published below, we get a .dataCorrupted error along with the main error:
struct PatchParameters: Swift.Encodable {
let name: Parameter?
}
func encoded(_ params: PatchParameters) -> String {
let data = try! JSONEncoder().encode(params)
return String(data: data, encoding: .utf8)!
}
encoded(PatchParameters(name: nil))
// prints "{}"
encoded(PatchParameters(name: .null))
//print "{"name":null}"
encoded(PatchParameters(name: .value("Alex")))
//print "{"name":"Alex"}"