Simplifying Data Serialization in Swift: Exploring Codable for iOS Dev — Part 1
Evangelist Apps
?? We craft mobile apps, full-stack web solutions & AI-powered innovations to accelerate business and digital success!
By Vinodhkumar Govindaraj , Senior iOS Developer at Evangelist Apps
Introduction:
Swift’s Codable protocol simplifies encoding and decoding custom data types in formats like JSON, Property List, or binary data. By conforming to Codable, you can effortlessly convert Swift objects or structs to serialized formats and vice versa.
To achieve Codable conformance, ensure that all properties in your type are Codable. Most basic types have default implementations provided by the Swift standard library, so you only need to handle custom or non-conforming types manually.
Here’s an example that demonstrates the usage of Codable in a Swift-based iOS app:
struct Person: Codable {
var name: String
var age: Int
}
// Encoding (converting an object to data)
let person = Person(name: "John", age: 25)
let encoder = JSONEncoder()
if let encodedData = try? encoder.encode(person) {
// Save or send the encodedData
print(encodedData)
}
// Decoding (converting data to an object)
let jsonString = """
{
"name": "Jane",
"age": 30
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
if let decodedPerson = try? decoder.decode(Person.self, from: jsonData!) {
// Use the decodedPerson object
print(decodedPerson.name) // Output: Jane
print(decodedPerson.age) // Output: 30
}
In the example above, the Person struct adopts the Codable protocol, allowing it to encode and decode data. The JSONEncoder converts the person object into JSON data, while the JSONDecoder decodes JSON data back into a Person object.
We can customise the encoding and decoding process by specifying key mappings, date formats, or handling nested objects. Let’s explore these options.
Although JSON is used in these examples, Codable can also work with other serialization formats like Property Lists. To use different formats, you can use classes such as PropertyListEncoder and PropertyListDecoder for encoding and decoding respectively.
Using Custom Keys in Codable: Mapping Swift Property Names to JSON?Keys
To handle custom keys in Swift’s Codable, you can use the CodingKeys enum. By specifying custom keys, you can map your Swift property names to different key names in the JSON representation. Here’s an example of how to use custom JSON keys in Codable:
struct Person: Codable {
var name: String
var age: Int
private enum CodingKeys: String, CodingKey {
case name = "full_name"
case age
}
}
// Encoding to JSON
let person = Person(name: "John Doe", age: 30)
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
if let encodedData = try? encoder.encode(person),
let jsonString = String(data: encodedData, encoding: .utf8) {
print(jsonString)
/*
Output:
{
"full_name" : "John Doe",
"age" : 30
}
*/
}
// Decoding from JSON
let json = """
{
"full_name": "Jane Smith",
"age": 25
}
"""
let decoder = JSONDecoder()
if let jsonData = json.data(using: .utf8),
let decodedPerson = try? decoder.decode(Person.self, from: jsonData) {
print(decodedPerson.name) // Output: Jane Smith
print(decodedPerson.age) // Output: 25
}
The Person struct includes a private nested enum called CodingKeys, which represents the properties of the struct. Each case in this enum corresponds to a property and can be assigned a custom string value to specify the associated JSON key name.
In the provided example, the name property is mapped to the JSON key?
“full_nameâ€, while the age property retains its original name. During encoding and decoding processes, these custom keys facilitate the mapping between the Swift properties and the JSON representation.
During the encoding of the Person struct to JSON, the resulting JSON will utilize the key “full_name†instead of `name`.
领英推è
When decoding JSON into a Person object, the JSON key “full_name†will be mapped to the name property.
By customizing the CodingKeys enum, you can effectively handle scenarios where the Swift property names differ from the corresponding JSON key names.
Handling Nested JSON Structures with Nested Keyed Containers in?Codable:
Nested JSON structures can be handled by utilizing nested keyed containers in the encode(to:) and init(from:) methods. This approach enables encoding and decoding of complex JSON objects with nested key-value pairs. Let’s take a look at an example showcasing the usage of nested keyed containers in Codable:
struct Person: Codable {
var name: String
var age: Int
var address: Address
struct Address: Codable {
var street: String
var city: String
}
enum CodingKeys: String, CodingKey {
case name
case age
case address
}
enum AddressCodingKeys: String, CodingKey {
case street
case city
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(age, forKey: .age)
var addressContainer = container.nestedContainer(keyedBy: AddressCodingKeys.self, forKey: .address)
try addressContainer.encode(address.street, forKey: .street)
try addressContainer.encode(address.city, forKey: .city)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
age = try container.decode(Int.self, forKey: .age)
let addressContainer = try container.nestedContainer(keyedBy: AddressCodingKeys.self, forKey: .address)
let street = try addressContainer.decode(String.self, forKey: .street)
let city = try addressContainer.decode(String.self, forKey: .city)
address = Address(street: street, city: city)
}
}
let person = Person(name: "John Doe", age: 30, address: Person.Address(street: "123 Main St", city: "New York"))
let encoder = JSONEncoder()
if let encodedData = try? encoder.encode(person),
let jsonString = String(data: encodedData, encoding: .utf8) {
print(jsonString)
// Output: {"name":"John Doe","address":{"street":"123 Main St","city":"New York"},"age":30}
}
let json = """
{"name":"Jane Smith","address":{"street":"456 Elm St","city":"San Francisco"},"age":25}
"""
let decoder = JSONDecoder()
if let jsonData = json.data(using: .utf8),
let decodedPerson = try? decoder.decode(Person.self, from: jsonData) {
print(decodedPerson.name) // Output: Jane Smith
print(decodedPerson.address.street) // Output: 456 Elm St
print(decodedPerson.address.city) // Output: San Francisco
}
In the given example, the Person struct includes a nested Address struct. To handle encoding and decoding, we define separate CodingKeys enums for both structs.
During encoding encode(to:) method, we create a top-level keyed container using the CodingKeys enum. We encode the name and age properties directly. For the address property, we create a nested keyed container using the AddressCodingKeys enum and encode the street and city properties.
During decoding init(from:) method, we follow a similar approach. We decode the name and age properties from the top-level keyed container. For the address property, we create a nested keyed container and decode the street and city properties.
By utilizing nested containers and the appropriate CodingKeys enums, complex JSON structures with nested data can be effectively handled.
Up Next:
Hope you enjoyed reading this article.
Cheers!