Model your classes with Argo JSON parser

The aim of this post is just basic introduction about how to use Argo framework for feeding your model classes from JSON data.

What is Argo.

[From https://github.com/thoughtbot/Argo]

Argo is a library that lets you extract models from JSON or similar structures in a way that’s concise, type-safe, and easy to extend. Using Argo, you won’t need to write validation code to ensure that incoming data is of the right type, or to make sure required data fields aren’t turning up empty. Argo uses Swift’s expressive type system to do that for you, and reports back explicit failure states in case it doesn’t find what you’ve told it to expect.

Argo

Argo is the Greek word for swift and the name of the ship used by Jason, son of Aeson, of the Argonauts. Aeson is the JSON parsing library in Haskell that inspired Argo, much like Aeson inspired his son Jason.

Codoapods. First things first.

In the Podfile you will have to add two frameworks, Argo itself and Curry that is being used for saving effort on initializing structs and classes.

# Uncomment this line to define a global platform for your project
# platform :ios, '8.0'
# Uncomment this line if you're using Swift
use_frameworks!

target 'ArgoDeSwift' do
pod 'Argo'
pod 'Curry'
end

 

Parsing structs

In this section through different examples I will explain how to parse the most common JSON structures.

Example 1. Most Basic JSON structure.

The content of the json file is the following:

{
"id": 34,
"name": "Pedroooo",
"email": "pe@fm.com"
}

The modeling struct is the following:

import Foundation
import Argo
import Curry

struct Person {
    let id: Int
    let name: String
    let email: String?
}

extension Person: Decodable {
    static func decode(j: JSON) -> Decoded<Person> {
        return curry(Person.init)
            <^> j <| "id"
            <*> j <| "name"
            <*> j <|? "email" // Use ? for parsing optional values
    }
}

Just a few comments

  • As you can see with curry you save up call the initializer passing all class attributes.
  • <^> is for marking the first attribute, the rest are marked with a <*>
  • <| is for parsing a regular attribute and <|? for the optional ones.

For parsing just:

let user: Decoded<Person> = decode(JSONFromFile("person")!)
  • “person” is just the filename that contains the json.
  • JSONFromFile is a helper method that retrieves the json from the file. At the end of the post you will find a like for downloading whole sample project.

Example 2. 1-n attributes.

The content of the json file is the following:

 [{
  "id": 34,
  "name": "Juan",
  "cars": []
  },{
  "id": 35,
  "name": "Paco",
  "cars": [{"year":2000,"brand":"citroen"}]
  },{
  "id": 36,
  "name": "Salvador",
  "cars": [{"year":2000,"brand":"peugeot"},{"year":2005,"brand":"renault"}]
  }]

The modeling struct is the following:

import Foundation

import Argo
import Curry

struct CarOwner {
    let id: Int
    let name: String
    let cars: [Car]?
}

extension CarOwner: Decodable {
    static func decode(j: JSON) -> Decoded<CarOwner> {
        return curry(CarOwner.init)
            <^> j <| "id"
            <*> j <| "name"
            <*> j <|| "cars" // Use ? for parsing optional values
    }
}

And this one:

import Foundation

import Argo
import Curry

struct Car {
    let year: Int
    let brand: String
}

extension Car: Decodable {
    static func decode(j: JSON) -> Decoded<Car> {
        return curry(Car.init)
            <^> j <| "year"
            <*> j <| "brand"
        
    }
}

Just a comment:

  • <|| is for parsing a collection of items

For parsing just:

let carOwners:Decoded<[CarOwner]> = decode(JSONFromFile("car_owners")!)

Example 4. Another way of parsing JSON file.

The content of json file is the following:

{
    "persons": [{
                "id": 34,
                "name": "Sergio",
                "email": "sg@fm.com"
                }, {
                "id": 35,
                "name": "Remedios",
                "email": "rm@fm.com"
                }],
    "cars": [{
             "year": 2000,
             "brand": "citroen"
             }, {
             "year": 2005,
             "brand": "renault"
             }]
}

The modeling struct:

import Foundation

import Argo
import Curry

struct PersonsAndCars {
    let persons: [Person]?
    let cars: [Car]?
}

extension PersonsAndCars: Decodable {
    static func decode(j: JSON) -> Decoded<PersonsAndCars> {
        return curry(PersonsAndCars.init)
            <^> j <|| "persons"
            <*> j <|| "cars"
    }
}

For parsing the struct just:

let model: PersonsAndCars? = JSONFromFile("persons_and_cars").flatMap(decode)

As you can see the way of calling parser in that case is different but the result is exactly the same.

Parsing classes

For parsing classes I am based on the last struct example, but for clear understanding I will show an example.

This is the json content:

{
"year": 34,
"name": "Chispa"
}

The modeling class:

import Foundation
import Argo
import Curry

class Pet{
    var year: Int = 0
    var name: String = ""
}

extension Pet : Decodable {
    typealias DecodedType = Pet
    
    static func decode(json: JSON) -> Decoded<Pet> {
        let pet = Pet()
        pet.year = (json <| "year").value!
        pet.name = (json <| "name").value!

        
        return .Success(pet)
    }
}

In deep the idea is quite close than with structures, but in case of classes is carried out in a different way.

Finally for calling the parser:

let json: AnyObject = JSONFromFile("pets")!
let pet = Pet.decode(JSON(json))

Conclusion

I hope that throught all those examples you will not find any difficulty for understanting how to do a basic parsing. Please take a look at the library documentation for deeper understanding. You can download the sample project from here.

Usefull links

 

Leave a Reply

Your email address will not be published. Required fields are marked *

2 × two =