Handling Optional Properties in JSON

In our previous versions of parser, there is no support for optionals. If any key transformation fails or if any key is missing or if the response contains nil values for keys, the whole parsing fails. This is not desired in most of the situations.

Posts in this series:

To handle the above cases, we need to add support for Optionals.

Following is the JSON that we want to parse:

// Our JSON to parse
[
  {
    "name": {
      "first_name": "Lex",
      "last_name": "Luthor"
    },
    "age": 28,
    "gender": "male",
    "address": {
      "city": "Edneyville",
      "state": "Maine"
    },
    "skills": [
      "laboris",
      "id",
      "consectetur",
      "duis",
      "incididunt",
      "eiusmod"
    ]
  },
  {
    "age": 29,
    "gender": "female",
    "address": {
      "street": "754 Marconi Place",
      "city": "Rosburg",
      "state": "Louisiana"
    },
    "skills": [
      "laborum",
      "et",
      "proident",
      "dolor",
      "exercitation",
      "nisi"
    ]
  }
]

If you observe carefully, there are 2 small (yet breaking) changes from our previous version:

  1. For the 1st person, address doesn’t have a street and as per our requirements, this is an invalid address
  2. For the 2nd person, name key is missing, which again leads to an invalid person

In both the cases, parsing will fail by returning nil objects. Let’s modify our Customer model to handle this nil value by making address property optional. Also, change the curried function so that it now takes an optional Address:

struct Customer {
    let name: String // first_name, last_name
    let age: Int
    let gender: Gender
    let address: Address?  // may be nil
    let skills: [String]
}

func makeCustomer(name: String) -> (age: Int) -> (gender: Gender) -> (address: Address?) -> (skills: [String]) -> Customer {
    return { age in { gender in { address in { skills in
        return Customer(name: name, age: age, gender: gender, address: address, skills: skills)
    }}}}
}

<?> Operator

We’ve used our custom operator <*> till now to push values into our curried model initializers. Unfortunately, it doesn’t support optional values (see the definition, it performs f(x) only if both f and x are present).

Overriding <*> with optional support didn’t help, Swift compiler got stuck for a very long duration during compilation. So I had to define a new operator <?> for this.

infix operator <?> {
    associativity left
    precedence 100
}

func <?><A, B>(f: (A? -> B)?, x: A?) -> B? {
    guard let f = f else {
        return nil
    }
    return f(x)
}

Please let me know if you can get this to work without creating a new operator.

This new operator <?> does almost same job as <*> except x can be an optional now. Because our function f accepts an optional value, the operator just passes the optional value x directly to f.

Parsing

Now that we’ve defined our new operator to handle Optional values, lets parse the model:

makeCustomer <*> get(data, key: "name")     <~~ parseName
             <*> get(data, key: "age")
             <*> get(data, key: "gender")   <~~ parseGender
             <?> get(data, key: "address")  <~~ parseAddress 
             <*> get(data, key: "skills")

The above code will parse the JSON into Customer model and if it couldn’t create Address model, it simply assigns nil value to address property of Customer.

There is one slight problem with this approach. This will only work if the whole property is optional ie., T?. You cannot have a property of type [T?] ie., array of Optional values. I’m experimenting few ideas (mostly not involving custom operators).

Please feel free to comment below, if you have suggestions and questions.

You can find the whole source code as Gist on GitHub