DEV Community

Jaume Viñas Navas
Jaume Viñas Navas

Posted on

3 2

Parsing JSON with Swift 5

There are great libraries out there that help you parse JSON data, but it is great to see how Swift 5 implements a fully-native solution to make your data types encodable and decodable for compatibility with JSON.

There are three new protocols in the Swift standard library that define an standardized approach to data encoding and decoding. These new protocols are Encodable, Decodable and Codable

Encoding and Decoding

The easiest way to encode or decode a type is to use standard properties that are already Codable, which can be types like String, Int, Double, Data, Date and URL.

Let’s consider a User structure that stores the name, the username and the phone number of a user.

struct User {
var name: String
var username: String
var phoneNumber: String
}

We can make it automatically decodable and encodable by conforming to the Codable protocol, which is a type alias of Encodable and Decodable.

struct User: Codable {
var name: String
var username: String
var phoneNumber: String
}

We can also use custom types that also conform to the Codable protocol, and use built-in types such as Array, Dictionary and Optional whenever they contain codable types.

Let’s add to the previous example a list of devices that the user owns. It is represented by an Array of Device. The Device stores the name and the manufacturer of a device.

struct Device: Codable {
var name: String
var manufacturer: String
}
struct User: Codable {
var name: String
var username: String
var phoneNumber: String
var devices: [Device]
}

We can now deserialize a JSON document into our User instance, and serialize that instance back into a JSON document.

func decode(data: Data) throws -> User? {
do {
let decoder = JSONDecoder()
let user = try decoder.decode(User.self, from: data)
return user
} catch let error {
print(error)
return nil
}
}
func encode(user: User) -> Data? {
do {
let encoder = JSONEncoder()
let data = try encoder.encode(user)
return data
} catch let error {
print(error)
return nil
}
}
func loadUser() -> User? {
guard let fileURL = Bundle.main.url(forResource: "user", withExtension: "json") else {
print("couldn't find the file")
return nil
}
do {
let content = try Data(contentsOf: fileURL)
let user = try decode(data: content)
return user
} catch let error {
print(error)
return nil
}
}
// We can decode a User from a json document
if let user = loadUser() {
print(user.name)
for device in user.devices {
print(device.name)
}
// We can encode the user
if let data = encode(user: user) {
print(data)
}
}

Encode or Decode Exclusively

In some cases, you may not need to decode and encode the same type. For example, you may only need to read data and decode them into an instance, or you just need to make API calls and there is no need to decode the response containing the same type.

You can achieve that by conforming Encodable and Decodable protocols. The example below shows how the User structure only encode data:

struct Device: Codable {
var name: String
var manufacturer: String
}
struct User: Encodable {
var name: String
var username: String
var phoneNumber: String
var devices: [Device]
}
func encode(user: User) -> Data? {
do {
let encoder = JSONEncoder()
let data = try encoder.encode(user)
return data
} catch let error {
print(error)
return nil
}
}
let device = Device(name: "iPhone 8 Plus", manufacturer: "Apple")
let user = User(name: "Mike Ross", username: "mross", phoneNumber: "555-523-234", devices: [device])
if let data = encode(user: user) {
print(data)
}

Custom Key Names

It is often the case that we parse JSON documents with properties that doesn’t match our property names. In that case, we need to define custom key names in order to parse these properties into our instances.

In order to define the custom list of key names we need to define an enumeration named CodingKeys which conforms to the CodingKeys protocol. The names of the enumeration cases should match the names you’ve given to the corresponding properties in your type.

Let’s update the property phoneNumber of our User structure, which is defined in the JSON document using snake-case style. The enumeration CodingKeys provide the phoneNumber alternative key specified by a String as the raw-value type for the CodingKeys enumeration.

struct Device: Codable {
var name: String
var manufacturer: String
}
struct User: Encodable {
var name: String
var username: String
var phoneNumber: String
var devices: [Device]
enum CodingKeys: String, CodingKey {
case name
case username
case phoneNumber = "phone_number"
case devices
}
}

If you find this post helpful, please recommend it for others to read.

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)