TestsTested | ✓ |
LangLanguage | SwiftSwift |
License | MIT |
ReleasedLast Release | Mar 2015 |
SPMSupports SPM | ✗ |
Maintained by Alexsander Akers.
Pistachio is a generic model framework for Swift. By leveraging lenses and value transformers, it allows you to create type safe adapters for any recursive data structure, be it JSON, YAML or XML.
If you are already familiar with Argo, take a look at Pistachiargo.
Let's start with defining a simple model:
struct Origin {
var city: String
init(city: String = "") {
self.city = city
}
}
struct Person {
var name: String
var origin: Origin
init(name: String = "", origin: Origin = Origin()) {
self.name = name
self.origin = origin
}
}
A lens is basically just a combination of a getter and a setter, providing a view on your model:
struct OriginLenses {
static let city = Lens(get: { $0.city }, set: { (inout origin: Origin, city) in
origin.city = city
})
}
struct PersonLenses {
static let name = Lens(get: { $0.name }, set: { (inout person: Person, name) in
person.name = name
})
static let origin = Lens(get: { $0.origin }, set: { (inout person: Person, origin) in
person.origin = origin
})
}
They can be used to access and modify your model:
var person = Person(name: "Felix", origin: Origin(city: "Berlin"))
person = set(PersonLenses.name, person, "Robb")
get(PersonLenses.name, person) // == "Robb"
And you can compose, lift, transform, ... them:
let composed = PersonLenses.origin >>> OriginLenses.city
person = set(composed, person, "New York")
get(composed, person) // == "New York"
var persons = [ person ]
let arrayLifted: Lens<[Person], [String]> = lift(composed)
persons = set(arrayLifted, [ person ], [ "San Francisco" ])
get(arrayLifted, persons) // == [ "San Francisco" ]
var result: Result<[Person], NSError> = success(persons)
let resultLifted: Lens<Result<[Person], NSError>, Result<[String], NSError>> = lift(arrayLifted)
result = set(resultLifted, result, success([ "London" ]))
get(resultLifted, result) // == .Success(Box([ "London" ]))
let valueTransformer: ValueTransformer<String, Int> = SocialSecurityNumberValueTransformer
let transformed = transform(PersonLenses.name, valueTransformer)
person = set(transformed, person, 1234567890)
get(PersonLenses.name, person) // == "Felix"
Value transformers can be flipped, composed and lifted:
let flipped = flip(valueTransformer)
flipped.transformedValue(1234567890) // == "Felix"
let composed = flipped >>> UppercaseValueTransformer
flipped.transformedValue(1234567890) // == "FELIX"
let dictionaryLifted = lift([ "Felix": 1234567890 ], 0, "Unknown")
dictionaryLifted.transformedValue("Felix") // == 1234567890
dictionaryLifted.transformedValue("Hans") // == 0
dictionaryLifted.reverseTransformedValue(1234567890) // == "Felix"
dictionaryLifted.reverseTransformedValue(0) // == "Unknown"
let optionalLifted = lift(UppercaseValueTransformer, "")
optionalLifted.transformedValue("Felix") // == "FELIX"
optionalLifted.transformedValue(nil) // == ""
optionalLifted.reverseTransformedValue("FELIX") // == "felix"
optionalLifted.reverseTransformedValue("") // == nil
let arrayLifted = lift(UppercaseValueTransformer)
arrayLifted.transformedValue([ "Felix", "Robb" ]) // == [ "FELIX", "ROBB" ]
With lenses and value transformers, you can create adapters for your models:
struct Adapters {
static let origin = DictionaryAdapter(specification: [
"city_name": transform(OriginLenses.city, StringToAnyObjectValueTransformers)
], dictionaryTansformer: DictionaryToAnyObjectValueTransformers)
static let person = DictionaryAdapter(specification: [
"name": transform(PersonLenses.name, StringToAnyObjectValueTransformers),
"origin": transform(PersonLenses.origin, lift(origin, Origin()))
], dictionaryTansformer: DictionaryToAnyObjectValueTransformers)
}
Uh, what was that? Right, the origin
adapter was lifted into a value transformer.
Use fix
to create adapters for recursive models:
let adapter: DictionaryAdapter<Model, Data, Error> = fix { adapter in
// use `adapter` to reference the currently created adapter
}
Adapters handle encoding to and decoding from data:
let adapter = Adapters.person
var person = Person(name: "Seb", origin: Origin(city: "Berlin"))
var data = adapter.encode(person)
// == .Success(Box([ "name": "Seb", "origin": [ "city_name": "Berlin" ] ]))
adapter.decode(Person(), from: data.value!)
// == .Success(Box(person))
Both encode
and decode
return a LlamaKit.Result
, which either holds the encoded/decoded value or an error. This enables you to gracefully handle coding errors.
Pistachio was built by Felix Jendrusch and Robert Böhnke. Greetings from Berlin