Search code examples
iosjsonswiftdo-catch

When to use do-catch block using Swift


In the following scenario when reading JSON data from a file, I have the following block of code:

// Fetch URL
let url = Bundle.main.url(forResource: "sampleJSON", withExtension: "json")!

// Load Data
let data = try! Data(contentsOf: url)

// Deserialize JSON
let json = try! JSONSerialization.jsonObject(with: data, options: [])

Is this block of code correct on its own, or should would it be better practice to include it inside a do-catch block? I'm asking because I have seen scenarios when pulling data from the web using URLSession, where developers do the JSONSerialization inside of a do-catch block. Is there a reason why for doing it when using URLSession, and not when simply pulling the JSON data from a file? What is best practice for something like this?


Solution

  • 1 - Is this block of code correct on its own, or should would it be better practice to include it inside a do-catch block?

    A: This code is correct. It will work if your sampleJSON.json file is in your bundle AND the data in your JSON file is correctly formated AND the JSONSerialization succeds parsing the data provided.

    2 - I'm asking because I have seen scenarios when pulling data from the web using URLSession, where developers do the JSONSerialization inside of a do-catch block. Is there a reason why for doing it when using URLSession, and not when simply pulling the JSON data from a file? What is best practice for something like this?

    A: The do-catch statement is seen more often when consuming data(JSON in this case) from the web because the API might break for any reason(wrong specification of the data that must be shown, error in the web application itself, etc) and if this happens we do not want our application to crash.

    I say CRASH because you used the ! which do not propagate the error to the upper layer of your application, it tries to force the operation and if fails would crash the app.

    At this point you probably realized that the reason you don't see do-catch statement when consuming data from your bundle is because the app developer himself provided the JSON so I'd assume you are sure about the content of the file, but I'd still use the do-catch statement since something could go wrong and don't want my app to crash due to a silly thing like this.

    TL;DR

    I recommend to ALWAYS use error propagation with throws/rethrows or even the ? so you can test for nil results.

    I have written a small article here with some tips and how it works in Swift 2.1, not much have changed in Swift 3.1 so you can use to study the do-catch statement.

    I would rewrite the code you provided like this:

    WARNING: UNTESTED CODE

    enum JSONFromFileError: Error {
        case fileNotInBundle(String)
        case deserializationError
        case getDataError(URL)
    }
    
    func json(from file: String) throws -> Any {
        // Fetch URL in Bundle
        guard let url = Bundle.main.url(forResource: file, withExtension: "json") else {
            throw JSONFromFileError.fileNotInBundle(file)
        }
    
        // Load Data from url
        guard let data = try? Data(contentsOf: url) else { 
            throw JSONFromFileError.getDataError(url)
        }
    
        // Deserialize JSON
        guard let json = try? JSONSerialization.jsonObject(with: data, options: []) else {
            throw JSONFromFileError.deserializationError
        }
    
        return json
    }
    
    do {
        let myJsonObject = try json(from: "sampleJSON")
        print(myJsonObject)
    } catch let error {
        print(error)
    }