A Codable Undefinable type for handling JSON undefined values

Undefinable

Overview

The purpose of this type is represent the JSON undefined state in Swift structs and classes.

The Undefinable enum is a generic with a Wrapped type, similar to Optional. The value is one of:

  • .undefined
  • .defined(Wrapped?)

When encoded with a KeyedEncodingContainer, an .undefined value will cause the entire key to be excluded from the encoded result.

When decoded with a KeyedDecodingContainer, an missing key will be decoded into an .undefined value.

null JSON values translate to .defined(nil)

Example

The best way to explain the user of Undefined is to demonstrate potential uses.

For example, consider the following simple struct:

struct User {
    var name: String
}

Now imagine in a framework like Vapor, you create a PATCH route which takes in a request like this:

struct UserUpdateRequest: Content {
    var name: String?
}

So it’s easy to allow the PATCH request to optionally update the name by doing something like:

if let name = request.name {
    user.name = name
}

Now what happens when we want to add an optional age to our User struct?

struct User {
    var name: String
    var age: Int? // The user can optionally provide their age.
}

So adding an optional age to the PATCH request will not work as we won’t know if it is nil because there was no change, or if it is nil because the user wants to explicitly set the age to nil. This is where we can use Undefinable.

struct UserUpdateRequest: Content {
    var name: String?
    var age: Undefinable<Int>
}

Now in our controller, we can do this:

    request.age.unwrap {
        user.age = $0
    }
}

If there was no JSON key in the request for age, the value would be .undefined and the closure for unwrap would not be executed. However if the key existed and was set to either a value or null, the closure would execute and $0 would be an Optional<Int>.

For convenience, there is also an infix operator to achieve the same thing:

    user.age ?= request.age

This will assign the unwrapped value to user.age as long as it was defined.

GitHub

View Github