About Swift 4 JSONEncoder and JSONDecoder extensions

By Fabian Buentello | Aug 03, 2017

With ChaiOne being a digital innovation agency, we are constantly finding ourselves on the bleeding edge of technology. Events such as WWDC becomes a department wide event in our offices. Developers from all different tech stacks can set time aside on their calendars to watch sessions and have discussions afterwards. This type of investment not only keeps ChaiOne up-to-date with what’s new in the tech world, but helps the growth and development of it’s engineers.

My favorite announcement from WWDC 2017 is better JSON parsing with the new classes JSONEncoder/JSONDecoder. This alone will clear out entire sections from Swift curated lists like matteocrippa and ioscookies. Today, I’m going to briefly go over what was introduced and show you some must have extensions when parsing JSON in Swift 4.

With that being said, feel free to download the code here so you can follow along.

Before going into Swift 4 code, let’s see how this was done in Swift 3.

do {
    if let data = data,
        let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
        // ok now you have a Dictionary<String:Any>, you still have to instantiate objects
    }
} catch {
    print("Error deserializing JSON: \(error)")
}

This is why frameworks like SwiftJSON were so popular, but the big problem would be the mapping from JSON to your object. This made other frameworks such as unbox very powerful.

Now with Swift 4, let’s say we have the following JSON:

[{
  "isActive": true,
  "age": 39,
  "name": {
      "first": "Jan",
      "last": "Monroe"
  },
  "email": "jan.monroe@providco.net"
}]

And here’s are model for it:

struct Person: Codable {
    struct Name: Codable {
        let first: String
        let last: String
    }
    let isActive: Bool
    let age: Int
    let name: Name
    let email: String
}

Pay attention to that Codable protocol, that’s pretty important! By having the Person struct conform to Codable we are saying this object can both encode and decode itself to an external representation. So what does that mean exactly? How does that help us? Well, that’s where the classes JSONDecoder and JSONEncoder come into play. JSONDecoder and JSONEncoder handle the mapping from a raw JSON object to your models.

In Swift 4, this is how you can parse JSON out to an object:

guard let file = Bundle.main.url(forResource: "File", withExtension: "json") else {
    throw("no file")
}

let data = try Data(contentsOf: file)

let decoder = JSONDecoder()
let people = try decoder.decode([Person].self, from: data)
print(people) // [Person(isActive: true, age: 39, name: .Person.Name(first: "Jan", last: "Monroe"), email: "jan.monroe@providco.net")]

But what if you want to store that object in UserDefaults or into a file? That’s where JSONEncoder comes into play:

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encodedPeople = try encoder.encode(people)
print(String(data: encodedPeople, encoding: .utf8)!) // [{"age":39,"isActive":true,"email":"Jan.Monroe@gmail.com","name":{"first":"Jan","last":"Monroe"}}]

Some of this could definitely become repetitive, plus I don’t like the whole [Person].self when decoding. Let’s clean this up by moving this code to extensions.

//: Decodable Extension

extension Decodable {
    static func decode(data: Data) throws -> Self {
        let decoder = JSONDecoder()
        return try decoder.decode(Self.self, from: data)
    }
}

//: Encodable Extension

extension Encodable {
    func encode() throws -> Data {
        let encoder = JSONEncoder()
        encoder.outputFormatting = .prettyPrinted
        return try encoder.encode(self)
    }
}

With these new extensions, this turns the above example into:

let people = try [Person].decode(data: data)
print(people) // [Person(isActive: true, age: 39, name: .Person.Name(first: "Jan", last: "Monroe"), email: "jan.monroe@providco.net")]

let jsonData = try people.encode()
print(String(data: jsonData, encoding: .utf8)!) // [{"age":39,"isActive":true,"email":"Jan.Monroe@gmail.com","name":{"first":"Jan","last":"Monroe"}}]

Don’t you feel that reads much clearer? Plus we got rid of the [Person].self too.

So there you have it, I hope you are just as excited to be working with these new classes as I am. Be sure to download the repo and share this post if you found this helpful!

Want to know more about ChaiOne and it’s culture? Check out our careers page and apply today!

Get in touch

Marketo Form