Building Breather (Part 4): Nested JSON Parsing with Decodable, CodingKeys and RxMoya’s filter and map

Breather is an open-source iOS app that shows the weather, air pollution and asthma conditions around you.

Alexandros Baramilis
6 min readMay 8, 2019

To raise awareness about air pollution and practice my iOS skills at the same time, I’m building an open-source app where I apply some of the latest knowledge I gain about iOS, Swift and programming. Since there is a lot of information to digest, I’m splitting it into small manageable parts:

AirVisual API’s nearest_city endpoint response

A successful response from AirVisual API’s (free tier) nearest_city endpoint looks like this:

https://api.airvisual.com/v2/nearest_city?lat=40.676906&lon=-73.942275&key=A5xEAXuhEFJZyZA4o

There is some data that we don’t need and many field names that could be more human readable, but Swift’s powerful protocols can help us decode the data in a nice and simple way 😊

Swift’s Decodable protocol

By creating structs that conform to the Decodable protocol and match the response fields we can easily parse the JSON into Swift objects.

The different structs reflect the different nesting levels of the JSON.

To conform to Decodable, the properties must either be of a Swift standard library type (ex. String, Int, Double), Foundation type (ex. Date, URL, Data) or a custom type that conforms to Decodable.

The latter makes handling nested JSON a piece of cake and accessing data becomes super easy as well. For example, if we want to access the humidity value, we just type: response.data.currentConditions.weather.humidity

If you want to go the other way i.e. from Swift type to JSON you can conform the Encodable protocol. If you want to do both, you can use the Codable typealias which makes you conform to both (typealias Codable = Decodable & Encodable). Apple has some good documentation on these protocols if you want to read more.

I like working with Moya and Decodable because they allow us to “mirror” APIs. In Part 3, we created the AirVisualAPI.swift file in the Networking group to “mirror” the AirVisual API. Now, we create the AirVisualNearestCityResponse.swift file in the Parsing group to “mirror” the endpoint response.

Btw, I’m following an [APIName][EndpointName]Response.swift naming pattern for the response files.

Side note: You might notice that the AirVisualWeather and AirVisualPollution parameters are identical to the Weather and Pollution parameters in our Model. This might be the case for now, but it could change in the future, so I prefer to keep the structs used for parsing responses separate from the model structs used in the rest of the app.

Swift’s CodingKey protocol

You probably also noticed that some of the property names used in our structs don’t match their corresponding JSON field names and that some JSON fields are completely missing from our structs.

The latter is ok, because we don’t need to use everything that is in the JSON response. This makes life easier if you have a big response but only need a couple of fields. But the former one is not. If you try to look for a property that does not match any field name in the JSON, the parsing will fail.

However, we can use Decodable with our own chosen property names by including a special enum in our structs named CodingKeys, which conforms to the String and CodingKey protocols.

You can either include them inside the structs themselves, or within extensions. I prefer to keep them separate as it makes visualising the nesting hierarchy of the structs easier.

Apple has written about CodingKeys as well in the same article I linked previously.

Propeller API’s forecast endpoint response

Let’s do the same for Propeller API’s forecast response.

A successful response looks like:

https://open.propellerhealth.com/prod/forecast?latitude=40.676906&longitude=-73.942275

We’re only interested in the properties field and its children, so our structs look like this:

Again, we use CodingKeys to turn code and value into risk and probability which represent their meaning better.

RxMoya’s filter and map operators

Now that we got our response structs, how do we parse the data?

With RxMoya, it’s as easy as adding two lines of code.

Where we left off in Part 3 — Bonus, we had an RxMoya request Single:

Now, we just add two operators in the chain:

.filterSuccessfulStatusCodes() is a custom Moya filter that will only let through status codes in the 200–299 range.

You also get other options like:

  • .filterSuccessfulStatusAndRedirectCodes()
  • .filter(statusCode: Int)
  • .filter(statusCodes: RangeExpression)

If a status code doesn’t fall in this range, a MoyaError is thrown and through the error handling flow that we built in Part 3, we get an error alert:

Now that we’re filtering out all the bad error codes, we can proceed to map our responses to our Decodable structs.

RxMoya has a handy .map(type: Decodable.Protocol) operator that decodes a Moya Response to any type that conforms to Decodable.

And there are also other map options like:

  • .mapImage()
  • .mapJSON()
  • .mapJSON(failsOnEmptyData: Bool)
  • .mapString()
  • .mapString(atKeyPath: String?)
  • .map(type: Decodable.Protocol, atKeyPath: String?, using: JSONDecoder, failsOnEmptyData: Bool)

If the conversion fails (ex. because we tried to convert a CodingKey that doesn’t exist), we get another MoyaError.

Similarly, for our propellerForecast Single, we do:

Merging the models and updating the UI

Finally, when we zip the two Observables to form a unified response and both sequences produce a next event, we get the two response structs that we created, of type AirVisualNearestCityResponse and PropellerForecastResponse. We use these two objects to create a new CityConditions object (our main model object) and inform the cityConditionsSubject which will then inform the View.

If you’re wondering what the getCity(), getWeather(), getPollution(), getAsthma() methods are, they’re just some helper methods I made as extensions to our response structs to move the bulk of the conversion code (from Parsing models -> Model) away from the ViewModel.

And now we can get the current weather, air quality and asthma conditions and update the UI:

Next

In this part we looked at how to handle successful API responses and easily parse the JSON and update the UI. We handled errors by showing generic Moya messages. In the next part we’ll look at handling API errors more specifically, with custom error messages, etc.

Keep reading → Part 4 (Bonus): Smooth API error handling with Moya & RxSwift custom operators.

--

--