Skip to content

Contexts

Brian Mullen edited this page Dec 4, 2016 · 2 revisions

Contexts can be very useful. A context can help us make logical decisions on state or provide us with helper methods to create dependent objects (i.e. Core Data).

Outlaw provides a context protocol for all of the standard protocols with the only difference being that you get an additional context parameter.

For the examples we will use the following types:

struct Address {
    var street: String
    var city: String
}
struct Person {
    var firstName: String
    var lastName: String
    var address: Address?
}

protocol AddressContext {}
protocol PersonContext {}
protocol PersonContextIncludeAddress: PersonContext, AddressContext {}

class SomeAddressContext: AddressContext {}
class SomePersonContext: PersonContext {}
class SomePersonContextIncludeAddress: PersonContextIncludeAddress {}
class NewObjectContext: PersonContextIncludeAddress {
    func newPerson() -> Person {
        return Person(firstName: "First", lastName: "Last", address: nil)
    }
    func newAddress() -> Address {
        return Address(street: "Street", city: "City")
    }
}

Example of using ValueWithContext

extension Address: ValueWithContext {
    static func value(from object: Any, using context: AddressContext) throws -> Address {
        guard let data = object as? Extractable else {
            throw OutlawError.typeMismatch(expected: Extractable.self, actual: type(of: object))
        }
        guard let newObjectContext = context as? NewObjectContext else {
            throw OutlawError.typeMismatch(expected: NewObjectContext.self, actual: type(of: context))
        }
        
        var address = newObjectContext.newAddress()
        address.street = try data.value(for: "street")
        address.city = try data.value(for: "city")
        
        return address
    }
}

extension Person: ValueWithContext {
    static func value(from object: Any, using context: PersonContext) throws -> Person {
        guard let data = object as? Extractable else {
            throw OutlawError.typeMismatch(expected: Extractable.self, actual: type(of: object))
        }
        guard let newObjectContext = context as? NewObjectContext else {
            throw OutlawError.typeMismatch(expected: NewObjectContext.self, actual: type(of: context))
        }
        
        var person = newObjectContext.newPerson()
        person.firstName = try data.value(for: "first")
        person.lastName = try data.value(for: "last")
        person.address = data.value(for: "address", using: newObjectContext)
        
        return person
    }
}

Now, we can create dependent objects (similar to what we would need to do with Core Data) and extract our object:

let context = NewObjectContext()
let person: Person = try! data.value(for: "personWithAddress", using: context)

Example of using SerializableWithContext

What if we want to exclude certain data when serializing? Lets setup the objects so we can do just that.

extension Address: SerializableWithContext {
    func serialized(using context: AddressContext) -> [String: Any] {
        return ["street": street,"city": city]
    }
}

extension Person: SerializableWithContext {
    func serialized(using context: PersonContext) -> [String: Any] {
        var result: [String: Any] = ["first": firstName,"last": lastName]
        
        if context is PersonContextIncludeAddress {
            if let address = address {
                result["address"] = address.serialized(using: context as! AddressContext)
            }
            else {
                result["address"] = [String: Any]()
            }
        }
        
        return result
    }
}

Now, we can exclude the address when we serialize by using a context:

let context = SomePersonContext()
let personData = person.serialized(using: context)
//personData == ["first": "FirstName", "last": "LastName"]

If we want to include the address, we just use a different context:

let context = SomePersonContextIncludeAddress()
let personData = person.serialized(using: context)
//personData == ["first": "FirstName", "last": "LastName", "address": ["street": "StreetName", "city": "CityName"]]

Additional information

As was mentioned, there is a context protocol for each standard protocol and the only difference is an additional context parameter. For more information about the context protocols, refer to the standard protocols documentation.

For ValueWithContext, DeserializableWithContext and IndexDeserializableWithContext refer to the documentation for Custom Types

For SerializableWithContext and IndexSerializableWithContext refer to the documentation for Serializing

For UpdatableWithContext and IndexUpdatableWithContext refer to the documentation for Updating

Clone this wiki locally