Parse JSON from file and URL with Swift
In todays tutorial we are going to learn how to read a JSON file with Swift, but that file could be a local JSON file or a hosted JSON file. I have a GitHub repo setup for this project that contains the local file but also the hosted file.
Before we get started we need to get our data.json
file setup. I would suggest copying the content from the file I have already created which you can find here.
Now that you have the file, we can start coding.
Step 1: Create the local JSON file
The first thing that we need to do is create our local JSON file. To add a blank file to our project we can simply select the blank file when creating a new file in Xcode.
The following images show the steps that you need to take to create the json file:
Next we need to select the blank file. To do this scroll all the way to the bottom of the file options:
The last thing we need to do is save the file. Make sure to name the file data.json
:
Step 2: Create the Codeable Struct
Now that we have our data.json
file we need to create our codeable
Struct that will represent the structure of our JSON data.
I have created a struct called DemoData
that represents the structure of the JSON data:
struct DemoData: Codable {
let title: String
let description: String
}
Step 3: Read the local file
Let's create the function that will read the local data.json
file. The function that we will create will take the file name as an argument and it will return Data?
as the return type.
The below code will allow us to read the file and return the data of the file:
private func readLocalFile(forName name: String) -> Data? {
do {
if let bundlePath = Bundle.main.path(forResource: name,
ofType: "json"),
let jsonData = try String(contentsOfFile: bundlePath).data(using: .utf8) {
return jsonData
}
} catch {
print(error)
}
return nil
}
There is nothing too complicated going on here. We will pass a file name as an argument and then use that file name to get a resource that matches the file name in our main bundle.
If this file exists in the main bundle we can use the bundlePath
when initializing a new String. This string will get the contents of the file and convert it to .utf8
data. When creating a new String with contentsOfFile
the initialization can fail. In order to handle this we wrap everything in a do try catch
statement.
If the file exists we will return the data from the file, if it doesn't, either because there was an error or the file doesn't exist, we will return nil.
Step 4: Create the JSON Parse method
The parsing method is super basic. Since Codeable
has been added to Swift, it has become incredibly simple to decode JSON. So in our parse
method we will be using the JSONDecoder().decode
method to convert our JSON to DemoData
.
This method is a bit dirty as we are going to use it for decoding as well as printing out the title
and the description
.
Add the following method to your code:
private func parse(jsonData: Data) {
do {
let decodedData = try JSONDecoder().decode(DemoData.self,
from: jsonData)
print("Title: ", decodedData.title)
print("Description: ", decodedData.description)
print("===================================")
} catch {
print("decode error")
}
}
Step 5: Create loadJson from URL method
We have created the method to read the local file, we have created the method to decode the content of the JSON file and print it to our console, so technically we should be able to run the app. Before we run the app I want us to create the method that will load the JSON file from a URL.
Below is the code for the loadJson
function:
private func loadJson(fromURLString urlString: String,
completion: @escaping (Result<Data, Error>) -> Void) {
if let url = URL(string: urlString) {
let urlSession = URLSession(configuration: .default).dataTask(with: url) { (data, response, error) in
if let error = error {
completion(.failure(error))
}
if let data = data {
completion(.success(data))
}
}
urlSession.resume()
}
}
The loadJson
method takes two arguments, the first being the urlString
and the second is completion
.
The completion takes an argument of type Result<Data, Error>
. Result
is a swift 5 feature and is perfect for this use case. If we have an error we can return .failure(error)
and if it is successful we can return .success(data)
when using the Result
type which we will see later on.
The first thing that we do is try and convert the urlString
to a URL
. This will return an optional URL
so we use if let
to unwrap the optional URL
.
If the URL
is not nil we will create a new URLSession
with a .default
configuration. We will then pass the url
to the dataTask
method, as well as a closure to handle the response. This response takes 3 arguments, data, response, error.
We then check if there is an error, if there is we call the completion with a .failure(error)
. If there is no error then we call the completion with .success(data)
. This makes it easier for us in the completion handler to handle the failed or successful result.
Step 6: Use the two methods we created
Now that we have both methods created we need to call them. Let's start with reading the local data.json
file.
To do this I have put the following code in the viewDidLoad
function:
if let localData = self.readLocalFile(forName: "data") {
self.parse(jsonData: localData)
}
It is super simple to use the readLocalFile
method. We pass through the file name and if there is data we will pass that data to our parse
method. When the parse
method runs and there are no errors, the title
and description
from our data.json
file will be printed to the console.
Now that we have read and printed the contents of our local JSON file, let's read and print the contents of the hosted JSON file.
Just below the code we added to read the local file, add the following code:
let urlString = "https://raw.githubusercontent.com/programmingwithswift/ReadJSONFileURL/master/hostedDataFile.json"
self.loadJson(fromURLString: urlString) { (result) in
switch result {
case .success(let data):
self.parse(jsonData: data)
case .failure(let error):
print(error)
}
}
We have create a new variable called urlString
, this is just to have things look a bit cleaner when we call loadJson
.
Next we call our loadJson
method. To do this we pass the urlString
through as well as a closure that takes one argument, Result<Data, Error>
.
Inside the closure we switch on the result
. The two cases that we have are .failure
and .success
, which are the same as what we had in our loadJson
method that we created in Step 5.
If the result is successful, we will pass the data
that we get from our loadJson
method and pass it to our parse
method. If loadJson
failed, we will print out the error
that it passed to this closure.
Now that all the code is done you can build and run the app. The content from both the local and the hosted files should be printed in your console.
Conclusion
Reading a JSON file that is local or one that requires a url is very easy with Swift 5 and later. Before it would have been a bit of an irritation, but codeable makes a world of difference, and with just a few lines we are able to read a JSON file from anywhere.
If you want to see the full source code, you can find it here.