Hosted by Microsoft Azure

Menu

Tag Archives: Alamofire

Recently I started a new iOS Swift project and spent way more time than I would like trying to find a JSON parser that could handle the various JSON data models I was working with. In this post I will document some real code samples that should prove useful for other iOS developers looking to get off to head start with data modelling in Swift.

The search for a Swift JSON parser…

Handling JSON is a very common task with modern app development whether its consuming some REST Service API, loading a JSON file or document objects from database. With regards to Windows C# apps Newtonsoft JSON is the popular choice and similarly with Java for Android there is GSON. But what library to use for iOS apps? Previously I had used libraries like JSONModel to parse JSON data into native objects and it worked pretty well. But the iOS developer landscape has changed with the shift from Objective C to Swift so I wanted to find a Swift based framework. There are a number of open source Swift JSON parsers, but the ones I tried resulted in code mountains just to parse some format of JSON. This felt like a fail compared to the elegant manner of Newtonsoft or GSON object models. I was surprised how hard it was to pinpoint the one Swift library that could satisfy all my parsing needs. But with Argo I feel I’ve discovered the golden JSON parsing library for iOS Swift development.

Getting on board with Argo

I’m a long time user of CocoaPods for Xcode source control projects as it makes it easier to avoid jamming up a repro with binaries. However the precompiled versions on CocoaPods don’t always provide the latest version available on GitHub. This is where Carthage comes in as you specifically request a tag version or branch on GitHub. Carthage can be quickly installed using Homebrewbrew install carthage as mentioned in the installing Carthage docs. To setup create a new text file and save it as ‘Cartfile’ inside your Xcode project folder. (In this case I’m requesting a specific version of Argo and Curry for use with Swift 2)

Cartfile

1

2

github"thoughtbot/Argo"==3.0.2

github"thoughtbot/Curry"==2.2

Once you have installed Carthage and saved a ‘Cartfile’ then you need to build the frameworks.

In Terminal navigate to the project folder and run carthage update to build frameworks for all platforms. NB: For packages that can only be built for a single platform use carthage build --platform iOS

JSON data modelling with Argo

Argo decodes standard property types (String, Int, UInt, Int64, UInt64, Double, Float, Bool) as well as arrays and optional properties. You can decode a nested object or an array of nested objects that conform to the ‘Decodable’ protocol. In fact you can even do inception – using the same struct within itself as shown below. One thing that might require explanation is Argo’s sugar syntax. The summary of the sugar syntax is this:

syntax pulls the first property, and pulls subsequent properties.

syntax relates to a property.

syntax relates to an optional property.

syntax relates to an array of 'decodable' objects.

syntax relates to an array of optional 'decodable' objects.

SomeModel.swift

Swift

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

importFoundation

importArgo

importCurry

structSomeModel{

letid:String

letname:String

lettotal:Int

letisHighlighted:Bool

letoptional:String?

letchildren:[SomeModel]?

}

extensionSomeModel: Decodable{

staticfuncdecode(j:JSON)->Decoded<SomeModel>{

returncurry(SomeModel.init)

<^>j<|"_id"

<*>j<|"name"

<*>j<|"total"

<*>j<|"highlighted"

<*>j<|?"optional"

<*>j<||?"children"

}

}

What about decoding JSON values into native types like NSURL and NSDate?

It can be advantageous to parse URL and date values as native types instead of String types. To get this to work with Argo you need to make a parser which wraps NSURL and NSDate in the 'Decoded' type. But first I made a Uri helper to encode url strings as NSURL and a Date helper to convert a date string (of a known format) to NSDate.

returnDecoded.Failure(DecodeError.Custom("Failed to parse String to NSURL"))

}

// Return NSURL wrapped in .Success

returnpure(url)

}

staticfunctoNSDate(dateString:String)->Decoded<NSDate>{

guard letdate=Date.StringToDate(dateString)else{

returnDecoded.Failure(DecodeError.Custom("Failed to parse String to NSDate"))

}

// Return NSDate wrapped in .Success

returnpure(date)

}

// optional (nil values are allowed)

staticfunctoOptionalNSDate(dateString:String?)->Decoded<NSDate?>{

guard letstr=dateStringelse{

returnpure(nil)// No date string

}

guard letdate=Date.StringToDate(str)else{

returnDecoded.Failure(DecodeError.Custom("Failed to parse String to NSDate"))

}

// Return NSDate wrapped in .Success

returnpure(date)

}

}

Example model with NSURL and NSDate using the Parser helper (note the extra brackets):

SomeModel.swift

Swift

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

importFoundation

importArgo

importCurry

structSomeModel{

letid:String

leturl:NSURL

letdateCreated:NSDate?

}

extensionSomeModel: Decodable{

staticfuncdecode(j:JSON)->Decoded<SomeModel>{

returncurry(SomeModel.init)

<^>j<|"_id"

<*>(j<|"url">>-Parser.toNSURL)

<*>(j<|?"date_created">>-Parser.toOptionalNSDate)

}

}

Three things to avoid in your JSON models for smoother sailing with Argo

Two dimensional arrays (arrays within an array) aren't handled out of the box. There are multi-dimensional array workarounds but it can cause compiler melt down if your model is particularly complex. Better to avoid this complexity by flattening arrays to a single array or use nested property arrays.

Best to limit object model to no more than 10 properties. This is because there are limits of how many things can be curried with Argo before the complier gives up. Try to use nested objects to group things together, but if that is not possible then there are techniques to deal with complex expressions.

Array of mixed objects (dynamic types). Argo can be made to decode an array of different types but it will increase complexity as you will have to use subclasses instead of structs.

How to load JSON file within iOS app bundle in Swift

Often the first thing I like to do is to load a JSON file to configure my app. For example you might have various JSON config files for localhost, staging and production settings.

The loaded JSON can be parsed into the 'ConfigModel' using Argo's decode method.

AppDelegate.swift

Swift

1

2

3

4

5

6

7

8

funcloadConfig(file:String)->ConfigModel?{

letjson:AnyObject?=loadJSON(file)

ifletj=json{

returndecode(j)

}

debugPrint("Error with \(file).json file")

returnnil

}

While this is fine for converting one type of object, what if you have multiple data models? You could quickly end up with a lot of repetitive code. One of the powerful things with Swift 2 is that it supports Abstract Types. Argo needs a little help to ensure the abstract type conforms to the Decodable type so there is slightly more boilerplate in this case, but it should help keep things DRY.

AppDelegate.swift

Swift

1

2

3

4

5

6

7

8

funcloadJSONFile<T:Decodable whereT==T.DecodedType>(file:String)->T?{

letjson:AnyObject?=loadJSON(file)

ifletj:AnyObject=json{

returndecode(j)

}

debugPrint("Error with \(file).json file")

returnnil

}

The JSON config file can be loaded in AppDelegate in the 'didFinishLaunchingWithOptions' method:

Parsing JSON response from REST service

I also needed to parse various JSON results provided by via REST service API. To handle the REST request here I'll be using the Alamofire library for Swift. Alamofire can also be added to the Cartfile:

Cartfile

1

github"Alamofire/Alamofire"~>3.4

Below is an example snippet taken from a login POST request. When using Alamofire the JSON data is available as response.result.value which can be parsed with the Argo decode method.

// Login was successful, do stuff here and then navigate to home screen...

}

}

One thing to point out: I have used very simple parse error detection here - it either decodes or it doesn't and there is no indication of what went wrong during the decode process. With smaller data models this form of indication is perfectly adequate. But when you are working with complex data models then this type of error reporting is not granular enough to pinpoint the exact the problem if you get a parse error. Fortunately Argo provides a way to parse with failure reporting by using a Decoded type.

I found this an absolutely invaluable technique to be able to debug issues with my complex models, especially as models are pretty verbose and its always hard to spot that one string mistake.

What's next…

What about storing loaded data for offline use? JSON documents can be stored with revisions using a Couchbase Lite database. The problem here is Argo only accommodates decode, but the native objects will need encoded back into JSON for use with Couchbase. This is where Ogra (Argo in reverse) comes in. The only thing is you will need to extend the data object with an encode method. If you found this post useful or if you would be interested to see some Ogra to Couch examples just fire me a tweet @deadlyfingers.